Merge "Add ofArgb to ObjectAnimator and ValueAnimator."
diff --git a/Android.mk b/Android.mk
index 50009c8b..10f9e3c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -169,7 +169,7 @@
 	core/java/android/print/ILayoutResultCallback.aidl \
 	core/java/android/print/IPrinterDiscoveryObserver.aidl \
 	core/java/android/print/IPrintDocumentAdapter.aidl \
-	core/java/android/print/IPrintClient.aidl \
+	core/java/android/print/IPrintDocumentAdapterObserver.aidl \
 	core/java/android/print/IPrintJobStateChangeListener.aidl \
 	core/java/android/print/IPrintManager.aidl \
 	core/java/android/print/IPrintSpooler.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 4e92015..2fa195c 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -183,6 +183,9 @@
 $(call add-clean-step, rm -f $(PRODUCT_OUT)/system/media/video/*)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/media/audio/effects/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/framework-res_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrintClient.*)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/current.txt b/api/current.txt
index 72b6011..0b605b5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,13 +21,10 @@
     field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
-    field public static final java.lang.String BIND_CALL_SERVICE = "android.permission.BIND_CALL_SERVICE";
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
-    field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
-    field public static final java.lang.String BIND_PRINT_SPOOLER_SERVICE = "android.permission.BIND_PRINT_SPOOLER_SERVICE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
@@ -80,7 +77,6 @@
     field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
     field public static final java.lang.String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS";
     field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
-    field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
     field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
     field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
@@ -272,6 +268,7 @@
     field public static final int addPrintersActivity = 16843750; // 0x10103e6
     field public static final int addStatesFromChildren = 16842992; // 0x10100f0
     field public static final int adjustViewBounds = 16843038; // 0x101011e
+    field public static final int advancedPrintOptionsActivity = 16843761; // 0x10103f1
     field public static final int alertDialogIcon = 16843605; // 0x1010355
     field public static final int alertDialogStyle = 16842845; // 0x101005d
     field public static final int alertDialogTheme = 16843529; // 0x1010309
@@ -525,7 +522,7 @@
     field public static final int format12Hour = 16843722; // 0x10103ca
     field public static final int format24Hour = 16843723; // 0x10103cb
     field public static final int fragment = 16843491; // 0x10102e3
-    field public static final int fragmentBreadCrumbsStyle = 16843761; // 0x10103f1
+    field public static final int fragmentBreadCrumbsStyle = 16843762; // 0x10103f2
     field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
     field public static final int fragmentCloseExitAnimation = 16843496; // 0x10102e8
     field public static final int fragmentFadeEnterAnimation = 16843497; // 0x10102e9
@@ -4417,6 +4414,8 @@
     method public void clear() throws java.io.IOException;
     method public void clearWallpaperOffsets(android.os.IBinder);
     method public void forgetLoadedWallpaper();
+    method public android.graphics.drawable.Drawable getBuiltInDrawable();
+    method public android.graphics.drawable.Drawable getBuiltInDrawable(int, int, boolean, float, float);
     method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri);
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
@@ -5810,6 +5809,7 @@
     method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
     method public static int getIsSyncable(android.accounts.Account, java.lang.String);
     method public static boolean getMasterSyncAutomatically();
+    method public java.util.List<android.content.UriPermission> getOutgoingPersistedUriPermissions();
     method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.accounts.Account, java.lang.String);
     method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.content.ComponentName);
     method public java.util.List<android.content.UriPermission> getPersistedUriPermissions();
@@ -6932,7 +6932,7 @@
     method public android.content.SyncRequest.Builder setSyncAdapter(android.accounts.Account, java.lang.String);
     method public android.content.SyncRequest.Builder setSyncAdapter(android.content.ComponentName);
     method public android.content.SyncRequest.Builder setTransferSize(long, long);
-    method public android.content.SyncRequest.Builder syncOnce(long, long);
+    method public android.content.SyncRequest.Builder syncOnce();
     method public android.content.SyncRequest.Builder syncPeriodic(long, long);
   }
 
@@ -7376,6 +7376,8 @@
     field public static final java.lang.String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
     field public static final java.lang.String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
     field public static final java.lang.String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+    field public static final java.lang.String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
+    field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
     field public static final java.lang.String FEATURE_SIP = "android.software.sip";
     field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip";
     field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
@@ -7567,11 +7569,13 @@
 
   public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
     ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long);
+    ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long, android.os.Bundle);
     method public void close() throws java.io.IOException;
     method public java.io.FileInputStream createInputStream() throws java.io.IOException;
     method public java.io.FileOutputStream createOutputStream() throws java.io.IOException;
     method public int describeContents();
     method public long getDeclaredLength();
+    method public android.os.Bundle getExtras();
     method public java.io.FileDescriptor getFileDescriptor();
     method public long getLength();
     method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
@@ -10776,10 +10780,6 @@
     method public int getMinFrequency();
   }
 
-  public abstract interface FlushCompleteListener {
-    method public abstract void onFlushCompleted(android.hardware.Sensor);
-  }
-
   public class GeomagneticField {
     ctor public GeomagneticField(float, float, float, long);
     method public float getDeclination();
@@ -10837,6 +10837,10 @@
     method public abstract void onSensorChanged(android.hardware.SensorEvent);
   }
 
+  public abstract interface SensorEventListener2 implements android.hardware.SensorEventListener {
+    method public abstract void onFlushCompleted(android.hardware.Sensor);
+  }
+
   public abstract deprecated interface SensorListener {
     method public abstract void onAccuracyChanged(int, int);
     method public abstract void onSensorChanged(int, float[]);
@@ -10844,7 +10848,7 @@
 
   public abstract class SensorManager {
     method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
-    method public boolean flush(android.hardware.Sensor);
+    method public boolean flush(android.hardware.SensorEventListener);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
@@ -10858,9 +10862,9 @@
     method public deprecated boolean registerListener(android.hardware.SensorListener, int);
     method public deprecated boolean registerListener(android.hardware.SensorListener, int, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
-    method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, int, android.hardware.FlushCompleteListener);
+    method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, android.os.Handler);
-    method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, int, android.os.Handler, android.hardware.FlushCompleteListener);
+    method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
     method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
@@ -11631,6 +11635,7 @@
     method public android.view.inputmethod.InputConnection getCurrentInputConnection();
     method public android.view.inputmethod.EditorInfo getCurrentInputEditorInfo();
     method public boolean getCurrentInputStarted();
+    method public int getInputMethodWindowRecommendedHeight();
     method public android.view.LayoutInflater getLayoutInflater();
     method public int getMaxWidth();
     method public java.lang.CharSequence getTextForImeAction(int);
@@ -19510,6 +19515,16 @@
     field public static final int STATE_STARTED = 3; // 0x3
   }
 
+  public static final class PrintJobInfo.Builder {
+    ctor public PrintJobInfo.Builder(android.print.PrintJobInfo);
+    method public android.print.PrintJobInfo build();
+    method public void putAdvancedOption(java.lang.String, java.lang.String);
+    method public void putAdvancedOption(java.lang.String, int);
+    method public void setAttributes(android.print.PrintAttributes);
+    method public void setCopies(int);
+    method public void setPages(android.print.PageRange[]);
+  }
+
   public final class PrintManager {
     method public java.util.List<android.print.PrintJob> getPrintJobs();
     method public android.print.PrintJob print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes);
@@ -19592,10 +19607,13 @@
     method public boolean cancel();
     method public boolean complete();
     method public boolean fail(java.lang.String);
+    method public int getAdvancedIntOption(java.lang.String);
+    method public java.lang.String getAdvancedStringOption(java.lang.String);
     method public android.printservice.PrintDocument getDocument();
     method public android.print.PrintJobId getId();
     method public android.print.PrintJobInfo getInfo();
     method public java.lang.String getTag();
+    method public boolean hasAdvancedOption(java.lang.String);
     method public boolean isBlocked();
     method public boolean isCancelled();
     method public boolean isCompleted();
@@ -19617,6 +19635,7 @@
     method protected void onDisconnected();
     method protected abstract void onPrintJobQueued(android.printservice.PrintJob);
     method protected abstract void onRequestCancelPrintJob(android.printservice.PrintJob);
+    field public static final java.lang.String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
     field public static final java.lang.String SERVICE_INTERFACE = "android.printservice.PrintService";
     field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
   }
@@ -22993,6 +23012,9 @@
   }
 
   public class Type extends android.renderscript.BaseObj {
+    method public static android.renderscript.Type createX(android.renderscript.RenderScript, android.renderscript.Element, int);
+    method public static android.renderscript.Type createXY(android.renderscript.RenderScript, android.renderscript.Element, int, int);
+    method public static android.renderscript.Type createXYZ(android.renderscript.RenderScript, android.renderscript.Element, int, int, int);
     method public int getCount();
     method public android.renderscript.Element getElement();
     method public int getX();
@@ -26276,6 +26298,7 @@
     method public boolean equals(android.util.DisplayMetrics);
     method public void setTo(android.util.DisplayMetrics);
     method public void setToDefaults();
+    field public static final int DENSITY_400 = 400; // 0x190
     field public static final int DENSITY_DEFAULT = 160; // 0xa0
     field public static final int DENSITY_HIGH = 240; // 0xf0
     field public static final int DENSITY_LOW = 120; // 0x78
@@ -29390,6 +29413,7 @@
 
   public final class AccessibilityManager {
     method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public deprecated java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
@@ -29397,6 +29421,7 @@
     method public boolean isEnabled();
     method public boolean isTouchExplorationEnabled();
     method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
   }
 
@@ -29404,6 +29429,10 @@
     method public abstract void onAccessibilityStateChanged(boolean);
   }
 
+  public static abstract interface AccessibilityManager.TouchExplorationStateChangeListener {
+    method public abstract void onTouchExplorationStateChanged(boolean);
+  }
+
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     method public void addAction(int);
     method public void addChild(android.view.View);
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index d8f9e49..188408d 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -191,13 +191,25 @@
     private long mChangingStagger = 0;
 
     /**
+     * Static interpolators - these are stateless and can be shared across the instances
+     */
+    private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
+            new AccelerateDecelerateInterpolator();
+    private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
+    private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+    private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+    private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
+    private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
+    private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
+
+    /**
      * The default interpolators used for the animations
      */
-    private TimeInterpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator();
-    private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
-    private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
-    private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
-    private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator();
+    private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
+    private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
+    private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
+    private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
+    private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
 
     /**
      * These hashmaps are used to store the animations that are currently running as part of
@@ -905,14 +917,24 @@
                     case APPEARING:
                         startDelay = mChangingAppearingDelay + staggerDelay;
                         staggerDelay += mChangingAppearingStagger;
+                        if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
+                            anim.setInterpolator(mChangingAppearingInterpolator);
+                        }
                         break;
                     case DISAPPEARING:
                         startDelay = mChangingDisappearingDelay + staggerDelay;
                         staggerDelay += mChangingDisappearingStagger;
+                        if (mChangingDisappearingInterpolator !=
+                                sChangingDisappearingInterpolator) {
+                            anim.setInterpolator(mChangingDisappearingInterpolator);
+                        }
                         break;
                     case CHANGING:
                         startDelay = mChangingDelay + staggerDelay;
                         staggerDelay += mChangingStagger;
+                        if (mChangingInterpolator != sChangingInterpolator) {
+                            anim.setInterpolator(mChangingInterpolator);
+                        }
                         break;
                 }
                 anim.setStartDelay(startDelay);
@@ -1148,6 +1170,9 @@
         anim.setTarget(child);
         anim.setStartDelay(mAppearingDelay);
         anim.setDuration(mAppearingDuration);
+        if (mAppearingInterpolator != sAppearingInterpolator) {
+            anim.setInterpolator(mAppearingInterpolator);
+        }
         if (anim instanceof ObjectAnimator) {
             ((ObjectAnimator) anim).setCurrentPlayTime(0);
         }
@@ -1192,6 +1217,9 @@
         Animator anim = mDisappearingAnim.clone();
         anim.setStartDelay(mDisappearingDelay);
         anim.setDuration(mDisappearingDuration);
+        if (mDisappearingInterpolator != sDisappearingInterpolator) {
+            anim.setInterpolator(mDisappearingInterpolator);
+        }
         anim.setTarget(child);
         final float preAnimAlpha = child.getAlpha();
         anim.addListener(new AnimatorListenerAdapter() {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 996b076..213ef77 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -61,6 +61,7 @@
 import android.util.AttributeSet;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.ActionMode;
@@ -1462,12 +1463,14 @@
     }
 
     /**
-     * Report to the system that your app is now fully drawn.  This is only used
-     * to help instrument app launch times, so that the app can report when it is
-     * fully in a usable state; without this, all the system can determine is when
-     * its window is first drawn and displayed.  To participate in app launch time
+     * Report to the system that your app is now fully drawn, purely for diagnostic
+     * purposes (calling it does not impact the visible behavior of the activity).
+     * This is only used to help instrument application launch times, so that the
+     * app can report when it is fully in a usable state; without this, the only thing
+     * the system itself can determine is the point at which the activity's window
+     * is <em>first</em> drawn and displayed.  To participate in app launch time
      * measurement, you should always call this method after first launch (when
-     * {@link #onCreate(android.os.Bundle)} is called) at the point where you have
+     * {@link #onCreate(android.os.Bundle)} is called), at the point where you have
      * entirely drawn your UI and populated with all of the significant data.  You
      * can safely call this method any time after first launch as well, in which case
      * it will simply be ignored.
@@ -4876,36 +4879,19 @@
                 writer.println(mChangingConfigurations);
         writer.print(innerPrefix); writer.print("mCurrentConfig=");
                 writer.println(mCurrentConfig);
+
         if (mLoaderManager != null) {
             writer.print(prefix); writer.print("Loader Manager ");
                     writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager)));
                     writer.println(":");
             mLoaderManager.dump(prefix + "  ", fd, writer, args);
         }
-        mFragments.dump(prefix, fd, writer, args);
-        writer.print(prefix); writer.println("View Hierarchy:");
-        dumpViewHierarchy(prefix + "  ", writer, getWindow().getDecorView());
-    }
 
-    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
-        writer.print(prefix);
-        if (view == null) {
-            writer.println("null");
-            return;
-        }
-        writer.println(view.toString());
-        if (!(view instanceof ViewGroup)) {
-            return;
-        }
-        ViewGroup grp = (ViewGroup)view;
-        final int N = grp.getChildCount();
-        if (N <= 0) {
-            return;
-        }
-        prefix = prefix + "  ";
-        for (int i=0; i<N; i++) {
-            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
-        }
+        mFragments.dump(prefix, fd, writer, args);
+
+        getWindow().getDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
+
+        mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1e65098..bb04063 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -68,9 +68,10 @@
     private final Handler mHandler;
 
     /**
-     * <meta-data> string for a 'home' Activity that names a package that is to be
+     * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
+     * &lt;meta-data>}</a> name for a 'home' Activity that declares a package that is to be
      * uninstalled in lieu of the declaring one.  The package named here must be
-     * signed with the same certificate as the one declaring the <meta-data>.
+     * signed with the same certificate as the one declaring the {@code &lt;meta-data>}.
      */
     public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
 
@@ -1463,7 +1464,10 @@
 
     /**
      * Permits an application to erase its own data from disk.  This is equivalent to
-     * the user choosing to clear the app's data from within the device settings UI.
+     * the user choosing to clear the app's data from within the device settings UI.  It
+     * erases all dynamic data associated with the app -- its private data and data in its
+     * private area on external storage -- but does not remove the installed application
+     * itself, nor any OBB files.
      *
      * @return {@code true} if the application successfully requested that the application's
      *     data be erased; {@code false} otherwise.
@@ -2253,7 +2257,9 @@
      * not be done on a UI thread.  The data will be written to the given file
      * descriptor as text.  An application must hold the
      * {@link android.Manifest.permission#DUMP} permission to make this call.
-     * @param fd The file descriptor that the dump should be written to.
+     * @param fd The file descriptor that the dump should be written to.  The file
+     * descriptor is <em>not</em> closed by this function; the caller continues to
+     * own it.
      * @param packageName The name of the package that is to be dumped.
      */
     public void dumpPackageState(FileDescriptor fd, String packageName) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 961ee57..74266cc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1160,7 +1160,10 @@
 
         case GET_PERSISTED_URI_PERMISSIONS_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
-            final ParceledListSlice<UriPermission> perms = getPersistedUriPermissions();
+            final String packageName = data.readString();
+            final boolean incoming = data.readInt() != 0;
+            final ParceledListSlice<UriPermission> perms = getPersistedUriPermissions(
+                    packageName, incoming);
             reply.writeNoException();
             perms.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
             return true;
@@ -3500,10 +3503,13 @@
     }
 
     @Override
-    public ParceledListSlice<UriPermission> getPersistedUriPermissions() throws RemoteException {
+    public ParceledListSlice<UriPermission> getPersistedUriPermissions(
+            String packageName, boolean incoming) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(packageName);
+        data.writeInt(incoming ? 1 : 0);
         mRemote.transact(GET_PERSISTED_URI_PERMISSIONS_TRANSACTION, data, reply, 0);
         reply.readException();
         final ParceledListSlice<UriPermission> perms = ParceledListSlice.CREATOR.createFromParcel(
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index df63ab3..347850a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -558,7 +558,7 @@
 
         public final void schedulePauseActivity(IBinder token, boolean finished,
                 boolean userLeaving, int configChanges) {
-            queueOrSendMessage(
+            sendMessage(
                     finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                     token,
                     (userLeaving ? 1 : 0),
@@ -567,32 +567,32 @@
 
         public final void scheduleStopActivity(IBinder token, boolean showWindow,
                 int configChanges) {
-           queueOrSendMessage(
+           sendMessage(
                 showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                 token, 0, configChanges);
         }
 
         public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
-            queueOrSendMessage(
+            sendMessage(
                 showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
                 token);
         }
 
         public final void scheduleSleeping(IBinder token, boolean sleeping) {
-            queueOrSendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
+            sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
         }
 
         public final void scheduleResumeActivity(IBinder token, int processState,
                 boolean isForward) {
             updateProcessState(processState, false);
-            queueOrSendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
         }
 
         public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
             ResultData res = new ResultData();
             res.token = token;
             res.results = results;
-            queueOrSendMessage(H.SEND_RESULT, res);
+            sendMessage(H.SEND_RESULT, res);
         }
 
         // we use token to identify this activity without having to send the
@@ -626,7 +626,7 @@
 
             updatePendingConfiguration(curConfig);
 
-            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
+            sendMessage(H.LAUNCH_ACTIVITY, r);
         }
 
         public final void scheduleRelaunchActivity(IBinder token,
@@ -641,12 +641,12 @@
             data.intents = intents;
             data.token = token;
 
-            queueOrSendMessage(H.NEW_INTENT, data);
+            sendMessage(H.NEW_INTENT, data);
         }
 
         public final void scheduleDestroyActivity(IBinder token, boolean finishing,
                 int configChanges) {
-            queueOrSendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
+            sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
                     configChanges);
         }
 
@@ -658,7 +658,7 @@
                     sync, false, mAppThread.asBinder(), sendingUser);
             r.info = info;
             r.compatInfo = compatInfo;
-            queueOrSendMessage(H.RECEIVER, r);
+            sendMessage(H.RECEIVER, r);
         }
 
         public final void scheduleCreateBackupAgent(ApplicationInfo app,
@@ -668,7 +668,7 @@
             d.compatInfo = compatInfo;
             d.backupMode = backupMode;
 
-            queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
+            sendMessage(H.CREATE_BACKUP_AGENT, d);
         }
 
         public final void scheduleDestroyBackupAgent(ApplicationInfo app,
@@ -677,7 +677,7 @@
             d.appInfo = app;
             d.compatInfo = compatInfo;
 
-            queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
+            sendMessage(H.DESTROY_BACKUP_AGENT, d);
         }
 
         public final void scheduleCreateService(IBinder token,
@@ -688,7 +688,7 @@
             s.info = info;
             s.compatInfo = compatInfo;
 
-            queueOrSendMessage(H.CREATE_SERVICE, s);
+            sendMessage(H.CREATE_SERVICE, s);
         }
 
         public final void scheduleBindService(IBinder token, Intent intent,
@@ -702,7 +702,7 @@
             if (DEBUG_SERVICE)
                 Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
                         + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
-            queueOrSendMessage(H.BIND_SERVICE, s);
+            sendMessage(H.BIND_SERVICE, s);
         }
 
         public final void scheduleUnbindService(IBinder token, Intent intent) {
@@ -710,7 +710,7 @@
             s.token = token;
             s.intent = intent;
 
-            queueOrSendMessage(H.UNBIND_SERVICE, s);
+            sendMessage(H.UNBIND_SERVICE, s);
         }
 
         public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
@@ -722,11 +722,11 @@
             s.flags = flags;
             s.args = args;
 
-            queueOrSendMessage(H.SERVICE_ARGS, s);
+            sendMessage(H.SERVICE_ARGS, s);
         }
 
         public final void scheduleStopService(IBinder token) {
-            queueOrSendMessage(H.STOP_SERVICE, token);
+            sendMessage(H.STOP_SERVICE, token);
         }
 
         public final void bindApplication(String processName,
@@ -763,24 +763,24 @@
             data.initProfileFile = profileFile;
             data.initProfileFd = profileFd;
             data.initAutoStopProfiler = false;
-            queueOrSendMessage(H.BIND_APPLICATION, data);
+            sendMessage(H.BIND_APPLICATION, data);
         }
 
         public final void scheduleExit() {
-            queueOrSendMessage(H.EXIT_APPLICATION, null);
+            sendMessage(H.EXIT_APPLICATION, null);
         }
 
         public final void scheduleSuicide() {
-            queueOrSendMessage(H.SUICIDE, null);
+            sendMessage(H.SUICIDE, null);
         }
 
         public void requestThumbnail(IBinder token) {
-            queueOrSendMessage(H.REQUEST_THUMBNAIL, token);
+            sendMessage(H.REQUEST_THUMBNAIL, token);
         }
 
         public void scheduleConfigurationChanged(Configuration config) {
             updatePendingConfiguration(config);
-            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
+            sendMessage(H.CONFIGURATION_CHANGED, config);
         }
 
         public void updateTimeZone() {
@@ -807,7 +807,7 @@
                 data.fd = ParcelFileDescriptor.dup(fd);
                 data.token = servicetoken;
                 data.args = args;
-                queueOrSendMessage(H.DUMP_SERVICE, data);
+                sendMessage(H.DUMP_SERVICE, data, 0, 0, true /*async*/);
             } catch (IOException e) {
                 Slog.w(TAG, "dumpService failed", e);
             }
@@ -825,11 +825,11 @@
         }
 
         public void scheduleLowMemory() {
-            queueOrSendMessage(H.LOW_MEMORY, null);
+            sendMessage(H.LOW_MEMORY, null);
         }
 
         public void scheduleActivityConfigurationChanged(IBinder token) {
-            queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
+            sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
         }
 
         public void profilerControl(boolean start, String path, ParcelFileDescriptor fd,
@@ -837,14 +837,14 @@
             ProfilerControlData pcd = new ProfilerControlData();
             pcd.path = path;
             pcd.fd = fd;
-            queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0, profileType);
+            sendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0, profileType);
         }
 
         public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) {
             DumpHeapData dhd = new DumpHeapData();
             dhd.path = path;
             dhd.fd = fd;
-            queueOrSendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0);
+            sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
         }
 
         public void setSchedulingGroup(int group) {
@@ -860,11 +860,11 @@
         }
 
         public void dispatchPackageBroadcast(int cmd, String[] packages) {
-            queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
+            sendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
         }
 
         public void scheduleCrash(String msg) {
-            queueOrSendMessage(H.SCHEDULE_CRASH, msg);
+            sendMessage(H.SCHEDULE_CRASH, msg);
         }
 
         public void dumpActivity(FileDescriptor fd, IBinder activitytoken,
@@ -875,7 +875,7 @@
                 data.token = activitytoken;
                 data.prefix = prefix;
                 data.args = args;
-                queueOrSendMessage(H.DUMP_ACTIVITY, data);
+                sendMessage(H.DUMP_ACTIVITY, data, 0, 0, true /*async*/);
             } catch (IOException e) {
                 Slog.w(TAG, "dumpActivity failed", e);
             }
@@ -888,7 +888,7 @@
                 data.fd = ParcelFileDescriptor.dup(fd);
                 data.token = providertoken;
                 data.args = args;
-                queueOrSendMessage(H.DUMP_PROVIDER, data);
+                sendMessage(H.DUMP_PROVIDER, data, 0, 0, true /*async*/);
             } catch (IOException e) {
                 Slog.w(TAG, "dumpProvider failed", e);
             }
@@ -1225,7 +1225,7 @@
 
         @Override
         public void unstableProviderDied(IBinder provider) {
-            queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
+            sendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
         }
 
         @Override
@@ -1235,7 +1235,7 @@
             cmd.activityToken = activityToken;
             cmd.requestToken = requestToken;
             cmd.requestType = requestType;
-            queueOrSendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
+            sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
         }
 
         private void printRow(PrintWriter pw, String format, Object...objs) {
@@ -1243,22 +1243,22 @@
         }
 
         public void setCoreSettings(Bundle coreSettings) {
-            queueOrSendMessage(H.SET_CORE_SETTINGS, coreSettings);
+            sendMessage(H.SET_CORE_SETTINGS, coreSettings);
         }
 
         public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) {
             UpdateCompatibilityData ucd = new UpdateCompatibilityData();
             ucd.pkg = pkg;
             ucd.info = info;
-            queueOrSendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
+            sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
         }
 
         public void scheduleTrimMemory(int level) {
-            queueOrSendMessage(H.TRIM_MEMORY, null, level);
+            sendMessage(H.TRIM_MEMORY, null, level);
         }
 
         public void scheduleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
-            queueOrSendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
+            sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
         }
 
         public void setProcessState(int state) {
@@ -1281,7 +1281,7 @@
 
         @Override
         public void scheduleInstallProvider(ProviderInfo provider) {
-            queueOrSendMessage(H.INSTALL_PROVIDER, provider);
+            sendMessage(H.INSTALL_PROVIDER, provider);
         }
     }
 
@@ -2033,28 +2033,31 @@
         mAppThread.scheduleSendResult(token, list);
     }
 
-    // if the thread hasn't started yet, we don't have the handler, so just
-    // save the messages until we're ready.
-    private void queueOrSendMessage(int what, Object obj) {
-        queueOrSendMessage(what, obj, 0, 0);
+    private void sendMessage(int what, Object obj) {
+        sendMessage(what, obj, 0, 0, false);
     }
 
-    private void queueOrSendMessage(int what, Object obj, int arg1) {
-        queueOrSendMessage(what, obj, arg1, 0);
+    private void sendMessage(int what, Object obj, int arg1) {
+        sendMessage(what, obj, arg1, 0, false);
     }
 
-    private void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
-        synchronized (this) {
-            if (DEBUG_MESSAGES) Slog.v(
-                TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
-                + ": " + arg1 + " / " + obj);
-            Message msg = Message.obtain();
-            msg.what = what;
-            msg.obj = obj;
-            msg.arg1 = arg1;
-            msg.arg2 = arg2;
-            mH.sendMessage(msg);
+    private void sendMessage(int what, Object obj, int arg1, int arg2) {
+        sendMessage(what, obj, arg1, arg2, false);
+    }
+
+    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
+        if (DEBUG_MESSAGES) Slog.v(
+            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+            + ": " + arg1 + " / " + obj);
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.obj = obj;
+        msg.arg1 = arg1;
+        msg.arg2 = arg2;
+        if (async) {
+            msg.setAsynchronous(true);
         }
+        mH.sendMessage(msg);
     }
 
     final void scheduleContextCleanup(ContextImpl context, String who,
@@ -2063,7 +2066,7 @@
         cci.context = context;
         cci.who = who;
         cci.what = what;
-        queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci);
+        sendMessage(H.CLEAN_UP_CONTEXT, cci);
     }
 
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
@@ -3592,7 +3595,7 @@
                     target.onlyLocalRequest = true;
                 }
                 mRelaunchingActivities.add(target);
-                queueOrSendMessage(H.RELAUNCH_ACTIVITY, target);
+                sendMessage(H.RELAUNCH_ACTIVITY, target);
             }
 
             if (fromServer) {
@@ -4900,7 +4903,7 @@
                                 mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                             mPendingConfiguration = newConfig;
                             
-                            queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
+                            sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                         }
                     }
                 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index dfea736..77c2ea0 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -215,7 +215,8 @@
             int mode) throws RemoteException;
     public void takePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException;
     public void releasePersistableUriPermission(Uri uri, int modeFlags) throws RemoteException;
-    public ParceledListSlice<UriPermission> getPersistedUriPermissions() throws RemoteException;
+    public ParceledListSlice<UriPermission> getPersistedUriPermissions(
+            String packageName, boolean incoming) throws RemoteException;
 
     public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
             throws RemoteException;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2bc7cbf..99eecb0 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -24,13 +24,16 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -47,6 +50,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
+import java.io.BufferedInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -69,6 +73,7 @@
      * an intent; instead, use {@link #getCropAndSetWallpaperIntent}.
      * <p>Input:  {@link Intent#getData} is the URI of the image to crop and set as wallpaper.
      * <p>Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise
+     * Activities that support this intent should specify a MIME filter of "image/*"
      */
     public static final String ACTION_CROP_AND_SET_WALLPAPER =
             "android.service.wallpaper.CROP_AND_SET_WALLPAPER";
@@ -216,24 +221,9 @@
         
         private static final int MSG_CLEAR_WALLPAPER = 1;
         
-        private final Handler mHandler;
-        
         Globals(Looper looper) {
             IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
             mService = IWallpaperManager.Stub.asInterface(b);
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case MSG_CLEAR_WALLPAPER:
-                            synchronized (this) {
-                                mWallpaper = null;
-                                mDefaultWallpaper = null;
-                            }
-                            break;
-                    }
-                }
-            };
         }
         
         public void onWallpaperChanged() {
@@ -242,7 +232,10 @@
              * to null so if the user requests the wallpaper again then we'll
              * fetch it.
              */
-            mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
+            synchronized (this) {
+                mWallpaper = null;
+                mDefaultWallpaper = null;
+            }
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
@@ -367,7 +360,7 @@
     
     /**
      * Retrieve the current system wallpaper; if
-     * no wallpaper is set, the system default wallpaper is returned.
+     * no wallpaper is set, the system built-in static wallpaper is returned.
      * This is returned as an
      * abstract Drawable that you can install in a View to display whatever
      * wallpaper the user has currently set. 
@@ -385,6 +378,178 @@
     }
 
     /**
+     * Returns a drawable for the system built-in static wallpaper .
+     *
+     */
+    public Drawable getBuiltInDrawable() {
+        return getBuiltInDrawable(0, 0, false, 0, 0);
+    }
+
+    /**
+     * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the
+     * drawable can be cropped and scaled
+     *
+     * @param outWidth The width of the returned drawable
+     * @param outWidth The height of the returned drawable
+     * @param scaleToFit If true, scale the wallpaper down rather than just cropping it
+     * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image;
+     *        0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned
+     * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image;
+     *        0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned
+     *
+     */
+    public Drawable getBuiltInDrawable(int outWidth, int outHeight,
+            boolean scaleToFit, float horizontalAlignment, float verticalAlignment) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return null;
+        }
+        Resources resources = mContext.getResources();
+        horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment));
+        verticalAlignment = Math.max(0, Math.min(1, verticalAlignment));
+
+        InputStream is = new BufferedInputStream(
+                resources.openRawResource(com.android.internal.R.drawable.default_wallpaper));
+
+        if (is == null) {
+            Log.e(TAG, "default wallpaper input stream is null");
+            return null;
+        } else {
+            if (outWidth <= 0 || outHeight <= 0) {
+                Bitmap fullSize = BitmapFactory.decodeStream(is, null, null);
+                return new BitmapDrawable(resources, fullSize);
+            } else {
+                int inWidth;
+                int inHeight;
+                {
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inJustDecodeBounds = true;
+                    BitmapFactory.decodeStream(is, null, options);
+                    if (options.outWidth != 0 && options.outHeight != 0) {
+                        inWidth = options.outWidth;
+                        inHeight = options.outHeight;
+                    } else {
+                        Log.e(TAG, "default wallpaper dimensions are 0");
+                        return null;
+                    }
+                }
+
+                is = new BufferedInputStream(resources.openRawResource(
+                        com.android.internal.R.drawable.default_wallpaper));
+
+                RectF cropRectF;
+
+                outWidth = Math.min(inWidth, outWidth);
+                outHeight = Math.min(inHeight, outHeight);
+                if (scaleToFit) {
+                    cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight,
+                        horizontalAlignment, verticalAlignment);
+                } else {
+                    float left = (inWidth - outWidth) * horizontalAlignment;
+                    float right = left + outWidth;
+                    float top = (inHeight - outHeight) * verticalAlignment;
+                    float bottom = top + outHeight;
+                    cropRectF = new RectF(left, top, right, bottom);
+                }
+                Rect roundedTrueCrop = new Rect();
+                cropRectF.roundOut(roundedTrueCrop);
+
+                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+                    Log.w(TAG, "crop has bad values for full size image");
+                    return null;
+                }
+
+                // See how much we're reducing the size of the image
+                int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth,
+                        roundedTrueCrop.height() / outHeight);
+
+                // Attempt to open a region decoder
+                BitmapRegionDecoder decoder = null;
+                try {
+                    decoder = BitmapRegionDecoder.newInstance(is, true);
+                } catch (IOException e) {
+                    Log.w(TAG, "cannot open region decoder for default wallpaper");
+                }
+
+                Bitmap crop = null;
+                if (decoder != null) {
+                    // Do region decoding to get crop bitmap
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    if (scaleDownSampleSize > 1) {
+                        options.inSampleSize = scaleDownSampleSize;
+                    }
+                    crop = decoder.decodeRegion(roundedTrueCrop, options);
+                    decoder.recycle();
+                }
+
+                if (crop == null) {
+                    // BitmapRegionDecoder has failed, try to crop in-memory
+                    is = new BufferedInputStream(resources.openRawResource(
+                            com.android.internal.R.drawable.default_wallpaper));
+                    Bitmap fullSize = null;
+                    if (is != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        if (scaleDownSampleSize > 1) {
+                            options.inSampleSize = scaleDownSampleSize;
+                        }
+                        fullSize = BitmapFactory.decodeStream(is, null, options);
+                    }
+                    if (fullSize != null) {
+                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+                                roundedTrueCrop.top, roundedTrueCrop.width(),
+                                roundedTrueCrop.height());
+                    }
+                }
+
+                if (crop == null) {
+                    Log.w(TAG, "cannot decode default wallpaper");
+                    return null;
+                }
+
+                // Scale down if necessary
+                if (outWidth > 0 && outHeight > 0 &&
+                        (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) {
+                    Matrix m = new Matrix();
+                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
+                    RectF returnRect = new RectF(0, 0, outWidth, outHeight);
+                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+                    if (tmp != null) {
+                        Canvas c = new Canvas(tmp);
+                        Paint p = new Paint();
+                        p.setFilterBitmap(true);
+                        c.drawBitmap(crop, m, p);
+                        crop = tmp;
+                    }
+                }
+
+                return new BitmapDrawable(resources, crop);
+            }
+        }
+    }
+
+    private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight,
+                float horizontalAlignment, float verticalAlignment) {
+        RectF cropRect = new RectF();
+        // Get a crop rect that will fit this
+        if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
+             cropRect.top = 0;
+             cropRect.bottom = inHeight;
+             float cropWidth = outWidth * (inHeight / (float) outHeight);
+             cropRect.left = (inWidth - cropWidth) * horizontalAlignment;
+             cropRect.right = cropRect.left + cropWidth;
+        } else {
+            cropRect.left = 0;
+            cropRect.right = inWidth;
+            float cropHeight = outHeight * (inWidth / (float) outWidth);
+            cropRect.top = (inHeight - cropHeight) * verticalAlignment;
+            cropRect.bottom = cropRect.top + cropHeight;
+        }
+        return cropRect;
+    }
+
+    /**
      * Retrieve the current system wallpaper; if there is no wallpaper set,
      * a null pointer is returned. This is returned as an
      * abstract Drawable that you can install in a View to display whatever
@@ -482,8 +647,19 @@
      * that supports cropping wallpapers, it will be preferred as the default.
      * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER}
      * intent.
+     *
+     * @param imageUri The image URI that will be set in the intent. The must be a content
+     *                 URI and its provider must resolve its type to "image/*"
+     *
+     * @throws IllegalArgumentException if the URI is not a content URI or its MIME type is
+     *         not "image/*"
      */
     public Intent getCropAndSetWallpaperIntent(Uri imageUri) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) {
+            throw new IllegalArgumentException("Image URI must be of the "
+                    + ContentResolver.SCHEME_CONTENT + " scheme type");
+        }
+
         final PackageManager packageManager = mContext.getPackageManager();
         Intent cropAndSetWallpaperIntent =
                 new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri);
@@ -505,7 +681,15 @@
 
         // fallback crop activity
         cropAndSetWallpaperIntent.setPackage("com.android.wallpapercropper");
-        return cropAndSetWallpaperIntent;
+        List<ResolveInfo> cropAppList = packageManager.queryIntentActivities(
+                cropAndSetWallpaperIntent, 0);
+        if (cropAppList.size() > 0) {
+            return cropAndSetWallpaperIntent;
+        }
+        // If the URI is not of the right type, or for some reason the system wallpaper
+        // cropper doesn't exist, return null
+        throw new IllegalArgumentException("Cannot use passed URI to set wallpaper; " +
+            "check that the type returned by ContentProvider matches image/*");
     }
 
     /**
@@ -519,7 +703,7 @@
      *
      * @param resid The bitmap to save.
      *
-     * @throws IOException If an error occurs reverting to the default
+     * @throws IOException If an error occurs reverting to the built-in
      * wallpaper.
      */
     public void setResource(int resid) throws IOException {
@@ -558,7 +742,7 @@
      *
      * @param bitmap The bitmap to save.
      *
-     * @throws IOException If an error occurs reverting to the default
+     * @throws IOException If an error occurs reverting to the built-in
      * wallpaper.
      */
     public void setBitmap(Bitmap bitmap) throws IOException {
@@ -597,7 +781,7 @@
      *
      * @param data A stream containing the raw data to install as a wallpaper.
      *
-     * @throws IOException If an error occurs reverting to the default
+     * @throws IOException If an error occurs reverting to the built-in
      * wallpaper.
      */
     public void setStream(InputStream data) throws IOException {
@@ -819,14 +1003,14 @@
     }
     
     /**
-     * Remove any currently set wallpaper, reverting to the system's default
+     * Remove any currently set wallpaper, reverting to the system's built-in
      * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
      * is broadcast.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER}.
      *
-     * @throws IOException If an error occurs reverting to the default
+     * @throws IOException If an error occurs reverting to the built-in
      * wallpaper.
      */
     public void clear() throws IOException {
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index e7e4a0f..6f929f2 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -513,7 +513,7 @@
         }
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothA2dp.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 5eb642c..5822e46 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -322,7 +322,8 @@
 
     /**
      * Broadcast Action: This intent is used to broadcast PAIRING REQUEST
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} to
+     * receive.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PAIRING_REQUEST =
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 1962514..8ee955d 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -885,7 +885,7 @@
         return false;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothHeadset.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index b1a084a..2e950fa 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -519,7 +519,7 @@
         mServiceListener = null;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothHealth.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index f9c789c..844f432 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -458,7 +458,7 @@
         return BluetoothProfile.PRIORITY_OFF;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothInputDevice.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index fac8fd5..92a2f1e 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -142,7 +142,6 @@
                 try {
                     mService = null;
                     mContext.unbindService(mConnection);
-                    mConnection = null;
                 } catch (Exception re) {
                     Log.e(TAG,"",re);
                 }
@@ -370,7 +369,7 @@
         return PRIORITY_OFF;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) log("Proxy object connected");
             mService = IBluetoothMap.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 83d4329..b7a37f4 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -155,23 +155,34 @@
 
     /*package*/ void close() {
         if (VDBG) log("close()");
-        if (mConnection != null) {
-            mContext.unbindService(mConnection);
-            mConnection = null;
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.w(TAG,"Unable to unregister BluetoothStateChangeCallback",re);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mPanService != null) {
+                try {
+                    mPanService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
         }
         mServiceListener = null;
-        try {
-            mAdapter.getBluetoothManager().unregisterStateChangeCallback(mStateChangeCallback);
-        } catch (RemoteException re) {
-            Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re);
-        }
     }
 
     protected void finalize() {
         close();
     }
 
-    private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() {
+    final private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() {
 
         @Override
         public void onBluetoothStateChange(boolean on) throws RemoteException {
@@ -339,7 +350,7 @@
         return false;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected");
             mPanService = IBluetoothPan.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c42251f..7f45652 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -197,7 +197,6 @@
                 try {
                     mService = null;
                     mContext.unbindService(mConnection);
-                    mConnection = null;
                 } catch (Exception re) {
                     Log.e(TAG,"",re);
                 }
@@ -300,7 +299,7 @@
         }
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) log("Proxy object connected");
             mService = IBluetoothPbap.Stub.asInterface(service);
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index a9b7176..ca7749d 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -143,11 +143,6 @@
     }
 
     @Override
-    public void captivePortalCheckComplete() {
-        // not implemented
-    }
-
-    @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         // not implemented
     }
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index eb7426e..7241e0d 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -20,7 +20,7 @@
 import android.os.Handler;
 import android.os.OperationCanceledException;
 import android.os.SystemClock;
-import android.util.Slog;
+import android.util.Log;
 import android.util.TimeUtils;
 
 import java.io.FileDescriptor;
@@ -64,10 +64,10 @@
         /* Runs on a worker thread */
         @Override
         protected D doInBackground(Void... params) {
-            if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
+            if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
             try {
                 D data = AsyncTaskLoader.this.onLoadInBackground();
-                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground");
+                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
                 return data;
             } catch (OperationCanceledException ex) {
                 if (!isCancelled()) {
@@ -79,7 +79,7 @@
                     // So we treat this case as an unhandled exception.
                     throw ex;
                 }
-                if (DEBUG) Slog.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
+                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
                 return null;
             }
         }
@@ -87,7 +87,7 @@
         /* Runs on the UI thread */
         @Override
         protected void onPostExecute(D data) {
-            if (DEBUG) Slog.v(TAG, this + " onPostExecute");
+            if (DEBUG) Log.v(TAG, this + " onPostExecute");
             try {
                 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
             } finally {
@@ -98,7 +98,7 @@
         /* Runs on the UI thread */
         @Override
         protected void onCancelled(D data) {
-            if (DEBUG) Slog.v(TAG, this + " onCancelled");
+            if (DEBUG) Log.v(TAG, this + " onCancelled");
             try {
                 AsyncTaskLoader.this.dispatchOnCancelled(this, data);
             } finally {
@@ -162,18 +162,18 @@
         super.onForceLoad();
         cancelLoad();
         mTask = new LoadTask();
-        if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
+        if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
         executePendingTask();
     }
 
     @Override
     protected boolean onCancelLoad() {
-        if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
+        if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
         if (mTask != null) {
             if (mCancellingTask != null) {
                 // There was a pending task already waiting for a previous
                 // one being canceled; just drop it.
-                if (DEBUG) Slog.v(TAG,
+                if (DEBUG) Log.v(TAG,
                         "cancelLoad: still waiting for cancelled task; dropping next");
                 if (mTask.waiting) {
                     mTask.waiting = false;
@@ -184,14 +184,14 @@
             } else if (mTask.waiting) {
                 // There is a task, but it is waiting for the time it should
                 // execute.  We can just toss it.
-                if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it");
+                if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
                 mTask.waiting = false;
                 mHandler.removeCallbacks(mTask);
                 mTask = null;
                 return false;
             } else {
                 boolean cancelled = mTask.cancel(false);
-                if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
+                if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
                 if (cancelled) {
                     mCancellingTask = mTask;
                     cancelLoadInBackground();
@@ -223,7 +223,7 @@
                 long now = SystemClock.uptimeMillis();
                 if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                     // Not yet time to do another load.
-                    if (DEBUG) Slog.v(TAG, "Waiting until "
+                    if (DEBUG) Log.v(TAG, "Waiting until "
                             + (mLastLoadCompleteTime+mUpdateThrottle)
                             + " to execute: " + mTask);
                     mTask.waiting = true;
@@ -231,7 +231,7 @@
                     return;
                 }
             }
-            if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
+            if (DEBUG) Log.v(TAG, "Executing: " + mTask);
             mTask.executeOnExecutor(mExecutor, (Void[]) null);
         }
     }
@@ -239,11 +239,11 @@
     void dispatchOnCancelled(LoadTask task, D data) {
         onCanceled(data);
         if (mCancellingTask == task) {
-            if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
+            if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
             rollbackContentChanged();
             mLastLoadCompleteTime = SystemClock.uptimeMillis();
             mCancellingTask = null;
-            if (DEBUG) Slog.v(TAG, "Delivering cancellation");
+            if (DEBUG) Log.v(TAG, "Delivering cancellation");
             deliverCancellation();
             executePendingTask();
         }
@@ -251,7 +251,7 @@
 
     void dispatchOnLoadComplete(LoadTask task, D data) {
         if (mTask != task) {
-            if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
+            if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
             dispatchOnCancelled(task, data);
         } else {
             if (isAbandoned()) {
@@ -261,7 +261,7 @@
                 commitContentChanged();
                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
                 mTask = null;
-                if (DEBUG) Slog.v(TAG, "Delivering result");
+                if (DEBUG) Log.v(TAG, "Delivering result");
                 deliverResult(data);
             }
         }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 461dc1d..a9d0559 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1078,6 +1078,7 @@
      * @see #openAssetFile(Uri, String)
      * @see #openFileHelper(Uri, String)
      * @see #getType(android.net.Uri)
+     * @see ParcelFileDescriptor#parseMode(String)
      */
     public ParcelFileDescriptor openFile(Uri uri, String mode)
             throws FileNotFoundException {
@@ -1147,6 +1148,7 @@
      * @see #openAssetFile(Uri, String)
      * @see #openFileHelper(Uri, String)
      * @see #getType(android.net.Uri)
+     * @see ParcelFileDescriptor#parseMode(String)
      */
     public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
             throws FileNotFoundException {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index fbf1fd0..018e4c5 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -368,9 +368,7 @@
     }
 
     /**
-     * <p>
      * Query the given URI, returning a {@link Cursor} over the result set.
-     * </p>
      * <p>
      * For best performance, the caller should follow these guidelines:
      * <ul>
@@ -405,9 +403,8 @@
     }
 
     /**
-     * <p>
-     * Query the given URI, returning a {@link Cursor} over the result set.
-     * </p>
+     * Query the given URI, returning a {@link Cursor} over the result set
+     * with optional support for cancellation.
      * <p>
      * For best performance, the caller should follow these guidelines:
      * <ul>
@@ -1627,9 +1624,9 @@
     }
 
     /**
-     * Take a persistable Uri permission grant that has been offered. Once
+     * Take a persistable URI permission grant that has been offered. Once
      * taken, the permission grant will be remembered across device reboots.
-     * Only Uri permissions granted with
+     * Only URI permissions granted with
      * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} can be persisted. If
      * the grant has already been persisted, taking it again will touch
      * {@link UriPermission#getPersistedTime()}.
@@ -1644,7 +1641,7 @@
     }
 
     /**
-     * Relinquish a persisted Uri permission grant. The Uri must have been
+     * Relinquish a persisted URI permission grant. The URI must have been
      * previously made persistent with
      * {@link #takePersistableUriPermission(Uri, int)}. Any non-persistent
      * grants to the calling package will remain intact.
@@ -1659,8 +1656,9 @@
     }
 
     /**
-     * Return list of all Uri permission grants that have been persisted for the
-     * calling app. Only persistable grants taken with
+     * Return list of all URI permission grants that have been persisted by the
+     * calling app. That is, the returned permissions have been granted
+     * <em>to</em> the calling app. Only persistable grants taken with
      * {@link #takePersistableUriPermission(Uri, int)} are returned.
      *
      * @see #takePersistableUriPermission(Uri, int)
@@ -1668,7 +1666,23 @@
      */
     public List<UriPermission> getPersistedUriPermissions() {
         try {
-            return ActivityManagerNative.getDefault().getPersistedUriPermissions().getList();
+            return ActivityManagerNative.getDefault()
+                    .getPersistedUriPermissions(mPackageName, true).getList();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Activity manager has died", e);
+        }
+    }
+
+    /**
+     * Return list of all persisted URI permission grants that are hosted by the
+     * calling app. That is, the returned permissions have been granted
+     * <em>from</em> the calling app. Only grants taken with
+     * {@link #takePersistableUriPermission(Uri, int)} are returned.
+     */
+    public List<UriPermission> getOutgoingPersistedUriPermissions() {
+        try {
+            return ActivityManagerNative.getDefault()
+                    .getPersistedUriPermissions(mPackageName, false).getList();
         } catch (RemoteException e) {
             throw new RuntimeException("Activity manager has died", e);
         }
@@ -1734,7 +1748,7 @@
             new SyncRequest.Builder()
                 .setSyncAdapter(account, authority)
                 .setExtras(extras)
-                .syncOnce(0, 0)     // Immediate sync.
+                .syncOnce()     // Immediate sync.
                 .build();
         requestSync(request);
     }
@@ -1908,13 +1922,13 @@
     public static void addPeriodicSync(Account account, String authority, Bundle extras,
             long pollFrequency) {
         validateSyncExtrasBundle(extras);
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
-        if (invalidPeriodicExtras(extras)) {
+        if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
+                || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
+                || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
+                || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
+                || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
+                || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
+                || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
             throw new IllegalArgumentException("illegal extras were set");
         }
         try {
@@ -1957,12 +1971,6 @@
      */
     public static void removePeriodicSync(Account account, String authority, Bundle extras) {
         validateSyncExtrasBundle(extras);
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
         try {
             getContentService().removePeriodicSync(account, authority, extras);
         } catch (RemoteException e) {
@@ -2005,12 +2013,6 @@
      * @return a list of PeriodicSync objects. This list may be empty but will never be null.
      */
     public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
-        if (account == null) {
-            throw new IllegalArgumentException("account must not be null");
-        }
-        if (authority == null) {
-            throw new IllegalArgumentException("authority must not be null");
-        }
         try {
             return getContentService().getPeriodicSyncs(account, authority, null);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 70eb3d8..32b95c2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,8 +34,10 @@
 import android.media.MediaScannerConnection.OnScanCompletedListener;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.StatFs;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
@@ -614,7 +616,7 @@
     public abstract File getFilesDir();
 
     /**
-     * Returns the absolute path to the directory on the external filesystem
+     * Returns the absolute path to the directory on the primary external filesystem
      * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
      * Environment.getExternalStorageDirectory()}) where the application can
      * place persistent files it owns.  These files are internal to the
@@ -628,10 +630,18 @@
      * <li>External files are not always available: they will disappear if the
      * user mounts the external storage on a computer or removes it.  See the
      * APIs on {@link android.os.Environment} for information in the storage state.
-     * <li>There is no security enforced with these files.  All applications
-     * can read and write files placed here.
+     * <li>There is no security enforced with these files.  For example, any application
+     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      *
+     * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to read or write to the returned path; it's always
+     * accessible to the calling app.  This only applies to paths generated for
+     * package name of the calling application.  To access paths belonging
+     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
+     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     *
      * <p>On devices with multiple users (as described by {@link UserManager}),
      * each user has their own isolated external storage. Applications only
      * have access to the external storage for the user they're running as.</p>
@@ -664,11 +674,6 @@
      *
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * private_picture}
-     * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * this path. Otherwise, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
-     * or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
      *
      * @param type The type of files directory to return.  May be null for
      * the root of the files directory or one of
@@ -698,36 +703,61 @@
      * it owns. These files are internal to the application, and not typically
      * visible to the user as media.
      * <p>
+     * This is like {@link #getFilesDir()} in that these files will be deleted when
+     * the application is uninstalled, however there are some important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
      * External storage devices returned here are considered a permanent part of
      * the device, including both emulated external storage and physical media
-     * slots. This does not include transient devices, such as USB flash drives.
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
      * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * these paths.
+     * An application may store data on any or all of the returned devices.  For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
      * <p>
-     * The returned paths include any path that would be returned by
-     * {@link #getExternalFilesDir(String)}.
+     * No permissions are required to read or write to the returned paths; they
+     * are always accessible to the calling app.  Write access outside of these
+     * paths on secondary external storage devices is not available.
+     * <p>
+     * The first path returned is the same as {@link #getExternalFilesDir(String)}.
+     * Returned paths may be {@code null} if a storage device is unavailable.
      *
      * @see #getExternalFilesDir(String)
+     * @see Environment#getStorageState(File)
      */
     public abstract File[] getExternalFilesDirs(String type);
 
     /**
-     * Return the directory where this application's OBB files (if there are
-     * any) can be found. Note if the application does not have any OBB files,
-     * this directory may not exist.
+     * Return the primary external storage directory where this application's OBB
+     * files (if there are any) can be found. Note if the application does not have
+     * any OBB files, this directory may not exist.
+     * <p>
+     * This is like {@link #getFilesDir()} in that these files will be deleted when
+     * the application is uninstalled, however there are some important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.  For example, any application
+     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
+     * </ul>
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to read or write to the returned path; it's always
+     * accessible to the calling app.  This only applies to paths generated for
+     * package name of the calling application.  To access paths belonging
+     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
+     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
      * <p>
      * On devices with multiple users (as described by {@link UserManager}),
      * multiple users may share the same OBB storage location. Applications
      * should ensure that multiple instances running under different users don't
      * interfere with each other.
-     * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * this path. Otherwise,
-     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} or
-     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
      */
     public abstract File getObbDir();
 
@@ -737,18 +767,32 @@
      * any) can be found. Note if the application does not have any OBB files,
      * these directories may not exist.
      * <p>
+     * This is like {@link #getFilesDir()} in that these files will be deleted when
+     * the application is uninstalled, however there are some important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
      * External storage devices returned here are considered a permanent part of
      * the device, including both emulated external storage and physical media
-     * slots. This does not include transient devices, such as USB flash drives.
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
      * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * this path.
+     * An application may store data on any or all of the returned devices.  For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
      * <p>
-     * The returned paths include any path that would be returned by
-     * {@link #getObbDir()}
+     * No permissions are required to read or write to the returned paths; they
+     * are always accessible to the calling app.  Write access outside of these
+     * paths on secondary external storage devices is not available.
+     * <p>
+     * The first path returned is the same as {@link #getObbDir()}.
+     * Returned paths may be {@code null} if a storage device is unavailable.
      *
      * @see #getObbDir()
+     * @see Environment#getStorageState(File)
      */
     public abstract File[] getObbDirs();
 
@@ -772,7 +816,7 @@
     public abstract File getCacheDir();
 
     /**
-     * Returns the absolute path to the directory on the external filesystem
+     * Returns the absolute path to the directory on the primary external filesystem
      * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
      * Environment.getExternalStorageDirectory()} where the application can
      * place cache files it owns. These files are internal to the application, and
@@ -794,19 +838,21 @@
      * <li>External files are not always available: they will disappear if the
      * user mounts the external storage on a computer or removes it.  See the
      * APIs on {@link android.os.Environment} for information in the storage state.
-     * <li>There is no security enforced with these files.  All applications
-     * can read and write files placed here.
+     * <li>There is no security enforced with these files.  For example, any application
+     * holding {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} can write to
+     * these files.
      * </ul>
      *
+     * <p>Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
+     * are required to read or write to the returned path; it's always
+     * accessible to the calling app.  This only applies to paths generated for
+     * package name of the calling application.  To access paths belonging
+     * to other packages, {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
+     * and/or {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
+     *
      * <p>On devices with multiple users (as described by {@link UserManager}),
      * each user has their own isolated external storage. Applications only
      * have access to the external storage for the user they're running as.</p>
-     * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * this path. Otherwise,
-     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} or
-     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} are required.
      *
      * @return The path of the directory holding application cache files
      * on external storage.  Returns null if external storage is not currently
@@ -824,18 +870,32 @@
      * owns. These files are internal to the application, and not typically
      * visible to the user as media.
      * <p>
+     * This is like {@link #getCacheDir()} in that these files will be deleted when
+     * the application is uninstalled, however there are some important differences:
+     * <ul>
+     * <li>External files are not always available: they will disappear if the
+     * user mounts the external storage on a computer or removes it.
+     * <li>There is no security enforced with these files.
+     * </ul>
+     * <p>
      * External storage devices returned here are considered a permanent part of
      * the device, including both emulated external storage and physical media
-     * slots. This does not include transient devices, such as USB flash drives.
+     * slots, such as SD cards in a battery compartment. The returned paths do
+     * not include transient devices, such as USB flash drives.
      * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no
-     * permissions are required for the owning application to read or write to
-     * these paths.
+     * An application may store data on any or all of the returned devices.  For
+     * example, an app may choose to store large files on the device with the
+     * most available space, as measured by {@link StatFs}.
      * <p>
-     * The returned paths include any path that would be returned by
-     * {@link #getExternalCacheDir()}.
+     * No permissions are required to read or write to the returned paths; they
+     * are always accessible to the calling app.  Write access outside of these
+     * paths on secondary external storage devices is not available.
+     * <p>
+     * The first path returned is the same as {@link #getExternalCacheDir()}.
+     * Returned paths may be {@code null} if a storage device is unavailable.
      *
      * @see #getExternalCacheDir()
+     * @see Environment#getStorageState(File)
      */
     public abstract File[] getExternalCacheDirs();
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f05e89a..85b3141 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -37,6 +37,8 @@
 import android.os.Parcelable;
 import android.os.StrictMode;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.provider.OpenableColumns;
 import android.util.AttributeSet;
 import android.util.Log;
 
@@ -48,7 +50,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -2624,49 +2625,76 @@
     public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
 
     /**
-     * Activity Action: Allow the user to select and open one or more existing
-     * documents. Both read and write access to the documents will be granted
-     * until explicitly revoked by the user.
+     * Activity Action: Allow the user to select and return one or more existing
+     * documents. When invoked, the system will display the various
+     * {@link DocumentsProvider} instances installed on the device, letting the
+     * user interactively navigate through them. These documents include local
+     * media, such as photos and video, and documents provided by installed
+     * cloud storage providers.
      * <p>
-     * Callers can restrict selection to a specific kind of data, such as
-     * photos, by setting one or more MIME types in {@link #EXTRA_MIME_TYPES}.
+     * Each document is represented as a {@code content://} URI backed by a
+     * {@link DocumentsProvider}, which can be opened as a stream with
+     * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for
+     * {@link android.provider.DocumentsContract.Document} metadata.
+     * <p>
+     * All selected documents are returned to the calling application with
+     * persistable read and write permission grants. If you want to maintain
+     * access to the documents across device reboots, you need to explicitly
+     * take the persistable permissions using
+     * {@link ContentResolver#takePersistableUriPermission(Uri, int)}.
+     * <p>
+     * Callers can restrict document selection to a specific kind of data, such
+     * as photos, by setting one or more MIME types in
+     * {@link #EXTRA_MIME_TYPES}.
      * <p>
      * If the caller can handle multiple returned items (the user performing
-     * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} to
-     * indicate this.
+     * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
+     * to indicate this.
      * <p>
      * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
      * returned URIs can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
-     * Output: The URI of the item that was picked. This must be a content: URI
-     * so that any receiver can access it. If multiple documents were selected,
-     * they are returned in {@link #getClipData()}.
+     * Output: The URI of the item that was picked. This must be a
+     * {@code content://} URI so that any receiver can access it. If multiple
+     * documents were selected, they are returned in {@link #getClipData()}.
      *
      * @see DocumentsContract
-     * @see DocumentsContract#getOpenDocuments(Context)
+     * @see #ACTION_CREATE_DOCUMENT
+     * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
 
     /**
-     * Activity Action: Allow the user to create a new document. Both read and
-     * write access to the document will be granted until explicitly revoked by
-     * the user.
+     * Activity Action: Allow the user to create a new document. When invoked,
+     * the system will display the various {@link DocumentsProvider} instances
+     * installed on the device, letting the user navigate through them. The
+     * returned document may be a newly created document with no content, or it
+     * may be an existing document with the requested MIME type.
      * <p>
-     * Callers can provide a hint document name by setting {@link #EXTRA_TITLE},
-     * but the user may change this value before creating the file. Callers can
-     * optionally hint at the MIME type being created by setting
-     * {@link #setType(String)}.
+     * Each document is represented as a {@code content://} URI backed by a
+     * {@link DocumentsProvider}, which can be opened as a stream with
+     * {@link ContentResolver#openFileDescriptor(Uri, String)}, or queried for
+     * {@link android.provider.DocumentsContract.Document} metadata.
+     * <p>
+     * Callers must indicate the concrete MIME type of the document being
+     * created by setting {@link #setType(String)}. This MIME type cannot be
+     * changed after the document is created.
+     * <p>
+     * Callers can provide an initial display name through {@link #EXTRA_TITLE},
+     * but the user may change this value before creating the file.
      * <p>
      * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
      * returned URIs can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
-     * Output: The URI of the item that was created. This must be a content: URI
-     * so that any receiver can access it.
+     * Output: The URI of the item that was created. This must be a
+     * {@code content://} URI so that any receiver can access it.
      *
      * @see DocumentsContract
+     * @see #ACTION_OPEN_DOCUMENT
+     * @see #FLAG_GRANT_PERSISTABLE_URI_PERMISSION
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -2790,11 +2818,16 @@
      * experience).
      */
     public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE";
+
     /**
-     * Used to indicate that a GET_CONTENT intent only wants URIs that can be opened with
-     * ContentResolver.openInputStream. Openable URIs must support the columns in
-     * {@link android.provider.OpenableColumns}
-     * when queried, though it is allowable for those columns to be blank.
+     * Used to indicate that an intent only wants URIs that can be opened with
+     * {@link ContentResolver#openFileDescriptor(Uri, String)}. Openable URIs
+     * must support at least the columns defined in {@link OpenableColumns} when
+     * queried.
+     *
+     * @see #ACTION_GET_CONTENT
+     * @see #ACTION_OPEN_DOCUMENT
+     * @see #ACTION_CREATE_DOCUMENT
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE";
@@ -3221,27 +3254,32 @@
             "android.intent.extra.client_intent";
 
     /**
-     * Used to indicate that a {@link #ACTION_GET_CONTENT} intent should only return
-     * data that is on the local device.  This is a boolean extra; the default
-     * is false.  If true, an implementation of ACTION_GET_CONTENT should only allow
-     * the user to select media that is already on the device, not requiring it
-     * be downloaded from a remote service when opened.  Another way to look
-     * at it is that such content should generally have a "_data" column to the
-     * path of the content on local external storage.
+     * Extra used to indicate that an intent should only return data that is on
+     * the local device. This is a boolean extra; the default is false. If true,
+     * an implementation should only allow the user to select data that is
+     * already on the device, not requiring it be downloaded from a remote
+     * service when opened.
+     *
+     * @see #ACTION_GET_CONTENT
+     * @see #ACTION_OPEN_DOCUMENT
+     * @see #ACTION_CREATE_DOCUMENT
      */
     public static final String EXTRA_LOCAL_ONLY =
-        "android.intent.extra.LOCAL_ONLY";
+            "android.intent.extra.LOCAL_ONLY";
 
     /**
-     * Used to indicate that a {@link #ACTION_GET_CONTENT} intent can allow the
-     * user to select and return multiple items.  This is a boolean extra; the default
-     * is false.  If true, an implementation of ACTION_GET_CONTENT is allowed to
-     * present the user with a UI where they can pick multiple items that are all
-     * returned to the caller.  When this happens, they should be returned as
-     * the {@link #getClipData()} part of the result Intent.
+     * Extra used to indicate that an intent can allow the user to select and
+     * return multiple items. This is a boolean extra; the default is false. If
+     * true, an implementation is allowed to present the user with a UI where
+     * they can pick multiple items that are all returned to the caller. When
+     * this happens, they should be returned as the {@link #getClipData()} part
+     * of the result Intent.
+     *
+     * @see #ACTION_GET_CONTENT
+     * @see #ACTION_OPEN_DOCUMENT
      */
     public static final String EXTRA_ALLOW_MULTIPLE =
-        "android.intent.extra.ALLOW_MULTIPLE";
+            "android.intent.extra.ALLOW_MULTIPLE";
 
     /**
      * The userHandle carried with broadcast intents related to addition, removal and switching of
@@ -3275,9 +3313,13 @@
             "android.intent.extra.restrictions_intent";
 
     /**
-     * Extra used to communicate set of acceptable MIME types for
-     * {@link #ACTION_GET_CONTENT} or {@link #ACTION_OPEN_DOCUMENT}. The type of the
-     * extra is <code>ArrayList&lt;String&gt;</code>.
+     * Extra used to communicate a set of acceptable MIME types. The type of the
+     * extra is {@code String[]}. Values may be a combination of concrete MIME
+     * types (such as "image/png") and/or partial MIME types (such as
+     * "audio/*").
+     *
+     * @see #ACTION_GET_CONTENT
+     * @see #ACTION_OPEN_DOCUMENT
      */
     public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
 
@@ -3304,7 +3346,7 @@
 
     /**
      * If set, the recipient of this Intent will be granted permission to
-     * perform read operations on the Uri in the Intent's data and any URIs
+     * perform read operations on the URI in the Intent's data and any URIs
      * specified in its ClipData.  When applying to an Intent's ClipData,
      * all URIs as well as recursive traversals through data or other ClipData
      * in Intent items will be granted; only the grant flags of the top-level
@@ -3313,7 +3355,7 @@
     public static final int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;
     /**
      * If set, the recipient of this Intent will be granted permission to
-     * perform write operations on the Uri in the Intent's data and any URIs
+     * perform write operations on the URI in the Intent's data and any URIs
      * specified in its ClipData.  When applying to an Intent's ClipData,
      * all URIs as well as recursive traversals through data or other ClipData
      * in Intent items will be granted; only the grant flags of the top-level
@@ -3348,7 +3390,7 @@
 
     /**
      * When combined with {@link #FLAG_GRANT_READ_URI_PERMISSION} and/or
-     * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the Uri permission grant can be
+     * {@link #FLAG_GRANT_WRITE_URI_PERMISSION}, the URI permission grant can be
      * persisted across device reboots until explicitly revoked with
      * {@link Context#revokeUriPermission(Uri, int)}. This flag only offers the
      * grant for possible persisting; the receiving application must call
@@ -3358,6 +3400,7 @@
      * @see ContentResolver#takePersistableUriPermission(Uri, int)
      * @see ContentResolver#releasePersistableUriPermission(Uri, int)
      * @see ContentResolver#getPersistedUriPermissions()
+     * @see ContentResolver#getOutgoingPersistedUriPermissions()
      */
     public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 0x00000040;
 
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 5760a5d..cd4a7a0 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -83,7 +83,9 @@
  * for an IntentFilter to match an Intent, three conditions must hold:
  * the <strong>action</strong> and <strong>category</strong> must match, and
  * the data (both the <strong>data type</strong> and
- * <strong>data scheme+authority+path</strong> if specified) must match.
+ * <strong>data scheme+authority+path</strong> if specified) must match
+ * (see {@link #match(ContentResolver, Intent, boolean, String)} for more details
+ * on how the data fields match).
  *
  * <p><strong>Action</strong> matches if any of the given values match the
  * Intent action; if the filter specifies no actions, then it will only match
@@ -722,6 +724,14 @@
      * included in the filter, then an Intent's data must match one of
      * them.  If no scheme specific parts are included, then only the scheme must match.
      *
+     * <p>The "scheme specific part" that this matches against is the string returned
+     * by {@link android.net.Uri#getSchemeSpecificPart() Uri.getSchemeSpecificPart}.
+     * For Uris that contain a path, this kind of matching is not generally of interest,
+     * since {@link #addDataAuthority(String, String)} and
+     * {@link #addDataPath(String, int)} can provide a better mechanism for matching
+     * them.  However, for Uris that do not contain a path, the authority and path
+     * are empty, so this is the only way to match against the non-scheme part.</p>
+     *
      * @param ssp Either a raw string that must exactly match the scheme specific part
      * path, or a simple pattern, depending on <var>type</var>.
      * @param type Determines how <var>ssp</var> will be compared to
@@ -968,7 +978,11 @@
      * Match this filter against an Intent's data (type, scheme and path). If
      * the filter does not specify any types and does not specify any
      * schemes/paths, the match will only succeed if the intent does not
-     * also specify a type or data.
+     * also specify a type or data.  If the filter does not specify any schemes,
+     * it will implicitly match intents with no scheme, or the schemes "content:"
+     * or "file:" (basically performing a MIME-type only match).  If the filter
+     * does not specify any MIME types, the Intent also must not specify a MIME
+     * type.
      *
      * <p>Be aware that to match against an authority, you must also specify a base
      * scheme the authority is in.  To match against a data path, both a scheme
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 911e49c..f3828b0 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -413,7 +413,7 @@
      * {@link #onReset()} happens.  You can retrieve the current abandoned
      * state with {@link #isAbandoned}.
      */
-    protected void onAbandon() {        
+    protected void onAbandon() {
     }
     
     /**
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 201a8b3..9894aad 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -319,37 +319,19 @@
         }
 
         /**
-         * Developer can define timing constraints for this one-shot request.
-         * These values are elapsed real-time.
-         *
-         * @param whenSeconds The time in seconds at which you want this
-         *            sync to occur.
-         * @param beforeSeconds The amount of time in advance of whenSeconds that this
-         *               sync may be permitted to occur. This is rounded up to a minimum of 5
-         *               seconds, for any sync for which whenSeconds > 5.
+         * Request that a sync occur immediately.
          *
          * Example
          * <pre>
-         *     Perform an immediate sync.
-         *     SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(0, 0);
-         *     That is, a sync 0 seconds from now with 0 seconds of flex.
-         *
-         *     Perform a sync in exactly 5 minutes.
-         *     SyncRequest.Builder builder =
-         *       new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 0);
-         *
-         *     Perform a sync in 5 minutes, with one minute of leeway (between 4 and 5 minutes from
-         *     now).
-         *     SyncRequest.Builder builder =
-         *       new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 1 * MIN_IN_SECS);
+         *     SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
          * </pre>
          */
-        public Builder syncOnce(long whenSeconds, long beforeSeconds) {
+        public Builder syncOnce() {
             if (mSyncType != SYNC_TYPE_UNKNOWN) {
                 throw new IllegalArgumentException("Sync type has already been defined.");
             }
             mSyncType = SYNC_TYPE_ONCE;
-            setupInterval(whenSeconds, beforeSeconds);
+            setupInterval(0, 0);
             return this;
         }
 
@@ -454,6 +436,9 @@
             if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
                 throw new IllegalArgumentException("Sync target has already been defined.");
             }
+            if (authority != null && authority.length() == 0) {
+                throw new IllegalArgumentException("Authority must be non-empty");
+            }
             mSyncTarget = SYNC_TARGET_ADAPTER;
             mAccount = account;
             mAuthority = authority;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6642c30..0192a30 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1034,6 +1034,20 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device includes a hardware step counter.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device includes a hardware step detector.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a telephony radio with data
      * communication support.
      */
@@ -3194,7 +3208,7 @@
     /**
      * 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/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index e4cc77f..28edde0 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,6 +16,7 @@
 
 package android.content.res;
 
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -42,17 +43,35 @@
     private final ParcelFileDescriptor mFd;
     private final long mStartOffset;
     private final long mLength;
-    
+    private final Bundle mExtras;
+
     /**
      * Create a new AssetFileDescriptor from the given values.
+     *
      * @param fd The underlying file descriptor.
      * @param startOffset The location within the file that the asset starts.
-     * This must be 0 if length is UNKNOWN_LENGTH.
+     *            This must be 0 if length is UNKNOWN_LENGTH.
      * @param length The number of bytes of the asset, or
-     * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+     *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
      */
     public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
             long length) {
+        this(fd, startOffset, length, null);
+    }
+
+    /**
+     * Create a new AssetFileDescriptor from the given values.
+     *
+     * @param fd The underlying file descriptor.
+     * @param startOffset The location within the file that the asset starts.
+     *            This must be 0 if length is UNKNOWN_LENGTH.
+     * @param length The number of bytes of the asset, or
+     *            {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
+     * @param extras additional details that can be used to interpret the
+     *            underlying file descriptor. May be null.
+     */
+    public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
+            long length, Bundle extras) {
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
         }
@@ -63,8 +82,9 @@
         mFd = fd;
         mStartOffset = startOffset;
         mLength = length;
+        mExtras = extras;
     }
-    
+
     /**
      * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
      * in addition to the normal FileDescriptor object also allows you to close
@@ -88,7 +108,15 @@
     public long getStartOffset() {
         return mStartOffset;
     }
-    
+
+    /**
+     * Returns any additional details that can be used to interpret the
+     * underlying file descriptor. May be null.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
     /**
      * Returns the total number of bytes of this asset entry's data.  May be
      * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
@@ -307,25 +335,37 @@
             super.write(oneByte);
         }
     }
-    
-    
+
     /* Parcelable interface */
+    @Override
     public int describeContents() {
         return mFd.describeContents();
     }
 
+    @Override
     public void writeToParcel(Parcel out, int flags) {
         mFd.writeToParcel(out, flags);
         out.writeLong(mStartOffset);
         out.writeLong(mLength);
+        if (mExtras != null) {
+            out.writeInt(1);
+            out.writeBundle(mExtras);
+        } else {
+            out.writeInt(0);
+        }
     }
 
     AssetFileDescriptor(Parcel src) {
         mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
         mStartOffset = src.readLong();
         mLength = src.readLong();
+        if (src.readInt() != 0) {
+            mExtras = src.readBundle();
+        } else {
+            mExtras = null;
+        }
     }
-    
+
     public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
             = new Parcelable.Creator<AssetFileDescriptor>() {
         public AssetFileDescriptor createFromParcel(Parcel in) {
diff --git a/core/java/android/hardware/FlushCompleteListener.java b/core/java/android/hardware/SensorEventListener2.java
similarity index 89%
rename from core/java/android/hardware/FlushCompleteListener.java
rename to core/java/android/hardware/SensorEventListener2.java
index fbdf4c8..4c3b429 100644
--- a/core/java/android/hardware/FlushCompleteListener.java
+++ b/core/java/android/hardware/SensorEventListener2.java
@@ -19,7 +19,7 @@
 /**
  * Used for receiving a notification when a flush() has been successfully completed.
  */
-public interface FlushCompleteListener {
+public interface SensorEventListener2 extends SensorEventListener {
     /**
      * Called after flush() is completed. This flush() could have been initiated by this application
      * or some other application. All the events in the batch at the point when the flush was called
@@ -28,7 +28,7 @@
      *
      * @param sensor The {@link android.hardware.Sensor Sensor} on which flush was called.
      *
-     * @see android.hardware.SensorManager#flush(Sensor)
+     * @see android.hardware.SensorManager#flush(SensorEventListener)
      */
     public void onFlushCompleted(Sensor sensor);
 }
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 8a4aa1d..b931313 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -533,8 +533,6 @@
      *
      * @see #unregisterListener(SensorEventListener)
      * @see #registerListener(SensorEventListener, Sensor, int)
-     *
-     * @throws IllegalArgumentException when sensor is a trigger sensor.
      */
     public void unregisterListener(SensorEventListener listener, Sensor sensor) {
         if (listener == null || sensor == null) {
@@ -601,7 +599,6 @@
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
      *
-     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs) {
         return registerListener(listener, sensor, rateUs, null);
@@ -638,7 +635,9 @@
      * {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead. </p>
      *
      * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object
-     *            that will receive the sensor events.
+     *            that will receive the sensor events. If the application is interested in receiving
+     *            flush complete notifications, it should register with
+     *            {@link android.hardware.SensorEventListener SensorEventListener2} instead.
      * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
      * @param rateUs The desired delay between two consecutive events in microseconds. This is only
      *            a hint to the system. Events may be received faster or slower than the specified
@@ -651,30 +650,23 @@
      *            large. If this is set to zero, batch mode is disabled and events are delivered in
      *            continuous mode as soon as they are available which is equivalent to calling
      *            {@link #registerListener(SensorEventListener, Sensor, int)}.
-     * @param reservedFlags Always set to Zero.
-     * @param flushCompleteListener A {@link android.hardware.FlushCompleteListener
-     *            FlushCompleteListener} object which is called when any application calls flush()
-     *            on this sensor and all the events in the batch at the time of calling flush() are
-     *            successfully delivered to the listeners.
-     * @return true if batch mode is successfully enabled for this sensor, false otherwise.
+     * @return <code>true</code> if batch mode is successfully enabled for this sensor,
+     *         <code>false</code> otherwise.
      * @see #registerListener(SensorEventListener, Sensor, int)
      * @see #unregisterListener(SensorEventListener)
-     * @see #flush(Sensor)
-     * @throws IllegalArgumentException when sensor or listener is null or a trigger sensor.
+     * @see #flush(SensorEventListener)
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
-            int maxBatchReportLatencyUs, int reservedFlags,
-            FlushCompleteListener flushCompleteListener) {
+            int maxBatchReportLatencyUs) {
         int delay = getDelay(rateUs);
-        return registerListenerImpl(listener, sensor, delay, null, maxBatchReportLatencyUs,
-                                        reservedFlags, flushCompleteListener);
+        return registerListenerImpl(listener, sensor, delay, null, maxBatchReportLatencyUs, 0);
     }
 
     /**
      * Registers a {@link android.hardware.SensorEventListener SensorEventListener} for the given
      * sensor. Events are delivered in continuous mode as soon as they are available. To reduce the
-     * battery usage, use {@link #registerListener(SensorEventListener, Sensor, int, int, int,
-     * FlushCompleteListener)} which enables batch mode for the sensor.
+     * battery usage, use {@link #registerListener(SensorEventListener, Sensor, int, int)} which
+     * enables batch mode for the sensor.
      *
      * <p class="note"></p>
      * Note: Don't use this method with a one shot trigger sensor such as
@@ -706,67 +698,80 @@
      *        {@link android.hardware.SensorEvent sensor events} will be
      *        delivered to.
      *
-     * @return true if the sensor is supported and successfully enabled.
+     * @return <code>true</code> if the sensor is supported and successfully enabled.
      *
      * @see #registerListener(SensorEventListener, Sensor, int)
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
-     *
-     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
             Handler handler) {
-        if (listener == null || sensor == null) {
-            return false;
-        }
-
         int delay = getDelay(rateUs);
-        return registerListenerImpl(listener, sensor, delay, handler, 0, 0, null);
+        return registerListenerImpl(listener, sensor, delay, handler, 0, 0);
     }
 
     /**
      * Enables batch mode for a sensor with the given rate and maxBatchReportLatency.
-     * @param handler
-     *        The {@link android.os.Handler Handler} the
-     *        {@link android.hardware.SensorEvent sensor events} will be
-     *        delivered to.
+     * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object
+     *            that will receive the sensor events. If the application is interested in receiving
+     *            flush complete notifications, it should register with
+     *            {@link android.hardware.SensorEventListener SensorEventListener2} instead.
+     * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+     * @param rateUs The desired delay between two consecutive events in microseconds. This is only
+     *            a hint to the system. Events may be received faster or slower than the specified
+     *            rate. Usually events are received faster. Can be one of
+     *            {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+     *            {@link #SENSOR_DELAY_GAME}, {@link #SENSOR_DELAY_FASTEST} or the delay in
+     *            microseconds.
+     * @param maxBatchReportLatencyUs An event in the batch can be delayed by at most
+     *            maxBatchReportLatency microseconds. More events can be batched if this value is
+     *            large. If this is set to zero, batch mode is disabled and events are delivered in
+     *            continuous mode as soon as they are available which is equivalent to calling
+     *            {@link #registerListener(SensorEventListener, Sensor, int)}.
+     * @param handler The {@link android.os.Handler Handler} the
+     *        {@link android.hardware.SensorEvent sensor events} will be delivered to.
      *
-     * @see #registerListener(SensorEventListener, Sensor, int, int, int, FlushCompleteListener)
+     * @return <code>true</code> if batch mode is successfully enabled for this sensor,
+     *         <code>false</code> otherwise.
+     * @see #registerListener(SensorEventListener, Sensor, int, int)
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
-            int maxBatchReportLatencyUs, int reservedFlags, Handler handler,
-            FlushCompleteListener flushCompleteListener) {
+            int maxBatchReportLatencyUs, Handler handler) {
         int delayUs = getDelay(rateUs);
-        return registerListenerImpl(listener, sensor, delayUs, handler, maxBatchReportLatencyUs,
-                                        reservedFlags, flushCompleteListener);
+        return registerListenerImpl(listener, sensor, delayUs, handler, maxBatchReportLatencyUs, 0);
     }
 
     /** @hide */
     protected abstract boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
-            int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags,
-            FlushCompleteListener flushCompleteListener);
+            int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags);
 
 
     /**
-     * Flushes the batch FIFO of the given sensor. If there are events in the FIFO of this sensor,
-     * they are returned as if the batch timeout has expired. Events are returned in the
-     * usual way through the SensorEventListener. This call doesn't effect the batch timeout for
-     * this sensor. This call is asynchronous and returns immediately. FlushCompleteListener is
-     * called after all the events in the batch at the time of calling this method have been
-     * delivered successfully.
-     * @param sensor
-     *        The {@link android.hardware.Sensor Sensor} to flush.
-     * @return true if the flush is initiated successfully. false if the sensor isn't active
-     *         i.e no application is registered for updates from this sensor.
-     * @see #registerListener(SensorEventListener, Sensor, int, int, int, FlushCompleteListener)
-     * @throws IllegalArgumentException when sensor is null or a trigger sensor.
+     * Flushes the batch FIFO of all the sensors registered for this listener. If there are events
+     * in the FIFO of the sensor, they are returned as if the batch timeout in the FIFO of the
+     * sensors had expired. Events are returned in the usual way through the SensorEventListener.
+     * This call doesn't affect the batch timeout for this sensor. This call is asynchronous and
+     * returns immediately.
+     * {@link android.hardware.SensorEventListener2#onFlushCompleted onFlushCompleted} is called
+     * after all the events in the batch at the time of calling this method have been delivered
+     * successfully. If the hardware doesn't support flush, it still returns true and a trivial
+     * flush complete event is sent after the current event for all the clients registered for this
+     * sensor.
+     *
+     * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object
+     *        which was previously used in a registerListener call.
+     * @return <code>true</code> if the flush is initiated successfully on all the sensors
+     *         registered for this listener, false if no sensor is previously registered for this
+     *         listener or flush on one of the sensors fails.
+     * @see #registerListener(SensorEventListener, Sensor, int, int)
+     * @throws IllegalArgumentException when listener is null.
      */
-    public boolean flush(Sensor sensor) {
-        return flushImpl(sensor);
+    public boolean flush(SensorEventListener listener) {
+        return flushImpl(listener);
     }
 
     /** @hide */
-    protected abstract boolean flushImpl(Sensor sensor);
+    protected abstract boolean flushImpl(SensorEventListener listener);
 
     /**
      * <p>
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 9747f0d..14f67c5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -93,17 +93,20 @@
     /** @hide */
     @Override
     protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
-            int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags,
-            FlushCompleteListener flushCompleteListener) {
-        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
-        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
-        if (reservedFlags != 0) throw new IllegalArgumentException("reservedFlags should be zero");
-        if (delayUs < 0) throw new IllegalArgumentException("rateUs should be positive");
-        if (maxBatchReportLatencyUs < 0)
-            throw new IllegalArgumentException("maxBatchReportLatencyUs should be positive");
+            int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) {
+        if (listener == null || sensor == null) {
+            Log.e(TAG, "sensor or listener is null");
+            return false;
+        }
         // Trigger Sensors should use the requestTriggerSensor call.
-        if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT)
-            throw new IllegalArgumentException("Trigger Sensors cannot use registerListener");
+        if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+            Log.e(TAG, "Trigger Sensors should use the requestTriggerSensor.");
+            return false;
+        }
+        if (maxBatchReportLatencyUs < 0 || delayUs < 0) {
+            Log.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative");
+            return false;
+        }
 
         // Invariants to preserve:
         // - one Looper per SensorEventListener
@@ -113,7 +116,7 @@
             SensorEventQueue queue = mSensorListeners.get(listener);
             if (queue == null) {
                 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
-                queue = new SensorEventQueue(listener, looper, this, flushCompleteListener);
+                queue = new SensorEventQueue(listener, looper, this);
                 if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags)) {
                     queue.dispose();
                     return false;
@@ -200,16 +203,17 @@
         }
     }
 
-    protected boolean flushImpl(Sensor sensor) {
-        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
-        if(Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT)
-            throw new IllegalArgumentException("Trigger Sensors cannot call flush");
+    protected boolean flushImpl(SensorEventListener listener) {
+        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
 
-        FlushEventQueue queue = new FlushEventQueue(mMainLooper, this);
-        if (queue.flushSensor(sensor) != 0) {
-            return false;
+        synchronized (mSensorListeners) {
+            SensorEventQueue queue = mSensorListeners.get(listener);
+            if (queue == null) {
+                return false;
+            } else {
+                return (queue.flush() == 0);
+            }
         }
-        return true;
     }
 
     /*
@@ -224,7 +228,7 @@
                 int maxBatchReportLatencyUs, int reservedFlags);
         private static native int nativeDisableSensor(int eventQ, int handle);
         private static native void nativeDestroySensorEventQueue(int eventQ);
-        private static native int nativeFlushSensor(int eventQ, int handle);
+        private static native int nativeFlushSensor(int eventQ);
         private int nSensorEventQueue;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
         protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
@@ -291,10 +295,9 @@
             return false;
         }
 
-        public int flushSensor(Sensor sensor) {
+        public int flush() {
             if (nSensorEventQueue == 0) throw new NullPointerException();
-            if (sensor == null) throw new NullPointerException();
-            return nativeFlushSensor(nSensorEventQueue, sensor.getHandle());
+            return nativeFlushSensor(nSensorEventQueue);
         }
 
         public boolean hasSensors() {
@@ -347,14 +350,12 @@
 
     static final class SensorEventQueue extends BaseEventQueue {
         private final SensorEventListener mListener;
-        private final FlushCompleteListener mFlushCompleteListener;
         private final SparseArray<SensorEvent> mSensorsEvents = new SparseArray<SensorEvent>();
 
         public SensorEventQueue(SensorEventListener listener, Looper looper,
-                SystemSensorManager manager, FlushCompleteListener flushCompleteListener) {
+                SystemSensorManager manager) {
             super(looper, manager);
             mListener = listener;
-            mFlushCompleteListener = flushCompleteListener;
         }
 
         public void addSensorEvent(Sensor sensor) {
@@ -408,9 +409,9 @@
 
         @SuppressWarnings("unused")
         protected void dispatchFlushCompleteEvent(int handle) {
-            final Sensor sensor = sHandleToSensor.get(handle);
-            if (mFlushCompleteListener != null) {
-                mFlushCompleteListener.onFlushCompleted(sensor);
+            if (mListener instanceof SensorEventListener2) {
+                final Sensor sensor = sHandleToSensor.get(handle);
+                ((SensorEventListener2)mListener).onFlushCompleted(sensor);
             }
             return;
         }
@@ -464,30 +465,4 @@
         protected void dispatchFlushCompleteEvent(int handle) {
         }
     }
-
-    static final class FlushEventQueue extends BaseEventQueue {
-        public FlushEventQueue(Looper looper, SystemSensorManager manager) {
-            super(looper, manager);
-        }
-
-        @SuppressWarnings("unused")
-        @Override
-        protected void dispatchSensorEvent(int handle, float[] values, int accuracy,
-                long timestamp) {
-        }
-
-        @Override
-        @SuppressWarnings("unused")
-        protected void addSensorEvent(Sensor sensor) {
-        }
-
-        @Override
-        @SuppressWarnings("unused")
-        protected void removeSensorEvent(Sensor sensor) {
-        }
-
-        @SuppressWarnings("unused")
-        protected void dispatchFlushCompleteEvent(int handle) {
-        }
-    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index adccbc5..072c5bb 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -92,15 +92,6 @@
     @SuppressWarnings("unchecked")
     @Override
     public <T> T get(Key<T> key) {
-
-        if (key == CaptureResult.STATISTICS_FACES) {
-            /**
-             * FIXME: Workaround for HAL bug that's missing FACE_DETECT_MODE
-             */
-            Log.w(TAG, "Expected non-null android.statistics.faceDetectMode");
-            return null;
-        }
-
         T value = getOverride(key);
         if (value != null) {
             return value;
@@ -452,10 +443,12 @@
     // and managed sides.
     @SuppressWarnings("unchecked")
     private <T> T getOverride(Key<T> key) {
-        if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) {
+        if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
             return (T) getAvailableFormats();
-        } else if (key == CaptureResult.STATISTICS_FACES) {
+        } else if (key.equals(CaptureResult.STATISTICS_FACES)) {
             return (T) getFaces();
+        } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
+            return (T) fixFaceRectangles();
         }
 
         // For other keys, get() falls back to getBase()
@@ -476,66 +469,102 @@
     private Face[] getFaces() {
         final int FACE_LANDMARK_SIZE = 6;
 
-        Integer faceDetectMode = getBase(CaptureResult.STATISTICS_FACE_DETECT_MODE);
+        Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE);
         if (faceDetectMode == null) {
-            throw new AssertionError("Expect non-null face detect mode");
-        }
-
-        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
-            return new Face[0];
-        }
-        if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE &&
-                faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
-            throw new AssertionError("Unknown face detect mode: " + faceDetectMode);
-        }
-
-        // Face scores and rectangles are required by SIMPLE and FULL mode.
-        byte[] faceScores = getBase(CaptureResult.STATISTICS_FACE_SCORES);
-        Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES);
-        if (faceScores == null || faceRectangles == null) {
-            throw new AssertionError("Expect face scores and rectangles to be non-null");
-        } else if (faceScores.length != faceRectangles.length) {
-            throw new AssertionError(
-                    String.format("Face score size(%d) doesn match face rectangle size(%d)!",
-                            faceScores.length, faceRectangles.length));
-        }
-
-        // Face id and landmarks are only required by FULL mode.
-        int[] faceIds = getBase(CaptureResult.STATISTICS_FACE_IDS);
-        int[] faceLandmarks = getBase(CaptureResult.STATISTICS_FACE_LANDMARKS);
-        int numFaces = faceScores.length;
-        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
-            if (faceIds == null || faceLandmarks == null) {
-                throw new AssertionError("Expect face ids and landmarks to be non-null for " +
-                        "FULL mode");
-            } else if (faceIds.length != numFaces ||
-                    faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) {
-                throw new AssertionError(
-                        String.format("Face id size(%d), or face landmark size(%d) don't match " +
-                                "face number(%d)!",
-                                faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE,
-                                numFaces));
+            Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
+            faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
+        } else {
+            if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
+                return new Face[0];
+            }
+            if (faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE &&
+                    faceDetectMode != CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+                Log.w(TAG, "Unknown face detect mode: " + faceDetectMode);
+                return new Face[0];
             }
         }
 
-        Face[] faces = new Face[numFaces];
+        // Face scores and rectangles are required by SIMPLE and FULL mode.
+        byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES);
+        Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        if (faceScores == null || faceRectangles == null) {
+            Log.w(TAG, "Expect face scores and rectangles to be non-null");
+            return new Face[0];
+        } else if (faceScores.length != faceRectangles.length) {
+            Log.w(TAG, String.format("Face score size(%d) doesn match face rectangle size(%d)!",
+                    faceScores.length, faceRectangles.length));
+        }
+
+        // To be safe, make number of faces is the minimal of all face info metadata length.
+        int numFaces = Math.min(faceScores.length, faceRectangles.length);
+        // Face id and landmarks are only required by FULL mode.
+        int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS);
+        int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS);
+        if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+            if (faceIds == null || faceLandmarks == null) {
+                Log.w(TAG, "Expect face ids and landmarks to be non-null for FULL mode," +
+                        "fallback to SIMPLE mode");
+                faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
+            } else {
+                if (faceIds.length != numFaces ||
+                        faceLandmarks.length != numFaces * FACE_LANDMARK_SIZE) {
+                    Log.w(TAG, String.format("Face id size(%d), or face landmark size(%d) don't" +
+                            "match face number(%d)!",
+                            faceIds.length, faceLandmarks.length * FACE_LANDMARK_SIZE, numFaces));
+                }
+                // To be safe, make number of faces is the minimal of all face info metadata length.
+                numFaces = Math.min(numFaces, faceIds.length);
+                numFaces = Math.min(numFaces, faceLandmarks.length / FACE_LANDMARK_SIZE);
+            }
+        }
+
+        ArrayList<Face> faceList = new ArrayList<Face>();
         if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) {
             for (int i = 0; i < numFaces; i++) {
-                faces[i] = new Face(faceRectangles[i], faceScores[i]);
+                if (faceScores[i] <= Face.SCORE_MAX &&
+                        faceScores[i] >= Face.SCORE_MIN) {
+                    faceList.add(new Face(faceRectangles[i], faceScores[i]));
+                }
             }
         } else {
             // CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL
             for (int i = 0; i < numFaces; i++) {
-                Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]);
-                Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]);
-                Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]);
-                faces[i] = new Face(faceRectangles[i], faceScores[i], faceIds[i],
-                        leftEye, rightEye, mouth);
+                if (faceScores[i] <= Face.SCORE_MAX &&
+                        faceScores[i] >= Face.SCORE_MIN &&
+                        faceIds[i] >= 0) {
+                    Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]);
+                    Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]);
+                    Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]);
+                    Face face = new Face(faceRectangles[i], faceScores[i], faceIds[i],
+                            leftEye, rightEye, mouth);
+                    faceList.add(face);
+                }
             }
         }
+        Face[] faces = new Face[faceList.size()];
+        faceList.toArray(faces);
         return faces;
     }
 
+    // Face rectangles are defined as (left, top, right, bottom) instead of
+    // (left, top, width, height) at the native level, so the normal Rect
+    // conversion that does (l, t, w, h) -> (l, t, r, b) is unnecessary. Undo
+    // that conversion here for just the faces.
+    private Rect[] fixFaceRectangles() {
+        Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES);
+        if (faceRectangles == null) return null;
+
+        Rect[] fixedFaceRectangles = new Rect[faceRectangles.length];
+        for (int i = 0; i < faceRectangles.length; i++) {
+            fixedFaceRectangles[i] = new Rect(
+                    faceRectangles[i].left,
+                    faceRectangles[i].top,
+                    faceRectangles[i].right - faceRectangles[i].left,
+                    faceRectangles[i].bottom - faceRectangles[i].top);
+        }
+        return fixedFaceRectangles;
+    }
+
     private <T> void setBase(Key<T> key, T value) {
         int tag = key.getTag();
 
@@ -559,7 +588,7 @@
 
     // Set the camera metadata override.
     private <T> boolean setOverride(Key<T> key, T value) {
-        if (key == CameraCharacteristics.SCALER_AVAILABLE_FORMATS) {
+        if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
             return setAvailableFormats((int[]) value);
         }
 
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 908aadd..01e5bac 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -19,7 +19,13 @@
 import android.view.Display;
 
 /**
- * Represents a virtual display.
+ * Represents a virtual display. The content of a virtual display is rendered to a
+ * {@link android.view.Surface} that you must provide to {@link DisplayManager#createVirtualDisplay
+ * createVirtualDisplay()}.
+ * <p>Because a virtual display renders to a surface provided by the application, it will be
+ * released automatically when the process terminates and all remaining windows on it will
+ * be forcibly removed. However, you should also explicitly call {@link #release} when you're
+ * done with it.
  *
  * @see DisplayManager#createVirtualDisplay
  */
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 9319d4a..81ad28b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -687,6 +687,8 @@
         mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
         mRootView = mInflater.inflate(
                 com.android.internal.R.layout.input_method, null);
+        mRootView.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mWindow.setContentView(mRootView);
         mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
         if (Settings.Global.getInt(getContentResolver(),
@@ -2320,6 +2322,21 @@
     }
 
     /**
+     * @return The recommended height of the input method window.
+     * An IME author can get the last input method's height as the recommended height
+     * by calling this in
+     * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}.
+     * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME
+     * switching by using this value as a visible inset height. It's efficient for the smooth
+     * transition between different IMEs. However, note that this may return 0 (or possibly
+     * unexpectedly low height). You should thus avoid relying on the return value of this method
+     * all the time. Please make sure to use a reasonable height for the IME.
+     */
+    public int getInputMethodWindowRecommendedHeight() {
+        return mImm.getInputMethodWindowVisibleHeight();
+    }
+
+    /**
      * Performs a dump of the InputMethodService's internal state.  Override
      * to add your own information to the dump.
      */
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 476fefe..804f8ee 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -20,10 +20,10 @@
 import android.os.Handler;
 import android.os.Messenger;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * Interface to control and observe state of a specific network, hiding
  * network-specific details from {@link ConnectivityManager}. Surfaces events
@@ -108,11 +108,6 @@
     }
 
     @Override
-    public void captivePortalCheckComplete() {
-        // not implemented
-    }
-
-    @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         // not implemented
     }
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index d678f1e..31b207c 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -84,13 +84,12 @@
     private String mServer;
     private String mUrl;
     private boolean mIsCaptivePortalCheckEnabled = false;
-    private IConnectivityManager mConnService;
-    private TelephonyManager mTelephonyManager;
-    private WifiManager mWifiManager;
-    private Context mContext;
+    private final IConnectivityManager mConnService;
+    private final TelephonyManager mTelephonyManager;
+    private final WifiManager mWifiManager;
+    private final Context mContext;
     private NetworkInfo mNetworkInfo;
 
-    private static final int CMD_DETECT_PORTAL          = 0;
     private static final int CMD_CONNECTIVITY_CHANGE    = 1;
     private static final int CMD_DELAYED_CAPTIVE_CHECK  = 2;
 
@@ -98,14 +97,15 @@
     private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
     private int mDelayedCheckToken = 0;
 
-    private State mDefaultState = new DefaultState();
-    private State mNoActiveNetworkState = new NoActiveNetworkState();
-    private State mActiveNetworkState = new ActiveNetworkState();
-    private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
+    private final State mDefaultState = new DefaultState();
+    private final State mNoActiveNetworkState = new NoActiveNetworkState();
+    private final State mActiveNetworkState = new ActiveNetworkState();
+    private final State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
 
     private static final String SETUP_WIZARD_PACKAGE = "com.google.android.setupwizard";
     private boolean mDeviceProvisioned = false;
-    private ProvisioningObserver mProvisioningObserver;
+    @SuppressWarnings("unused")
+    private final ProvisioningObserver mProvisioningObserver;
 
     private CaptivePortalTracker(Context context, IConnectivityManager cs) {
         super(TAG);
@@ -174,29 +174,11 @@
         return captivePortal;
     }
 
-    public void detectCaptivePortal(NetworkInfo info) {
-        sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
-    }
-
     private class DefaultState extends State {
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) log(getName() + message.toString());
-            switch (message.what) {
-                case CMD_DETECT_PORTAL:
-                    NetworkInfo info = (NetworkInfo) message.obj;
-                    // Checking on a secondary connection is not supported
-                    // yet
-                    notifyPortalCheckComplete(info);
-                    break;
-                case CMD_CONNECTIVITY_CHANGE:
-                case CMD_DELAYED_CAPTIVE_CHECK:
-                    break;
-                default:
-                    loge("Ignoring " + message);
-                    break;
-            }
+            loge("Ignoring " + message);
             return HANDLED;
         }
     }
@@ -316,19 +298,6 @@
         }
     }
 
-    private void notifyPortalCheckComplete(NetworkInfo info) {
-        if (info == null) {
-            loge("notifyPortalCheckComplete on null");
-            return;
-        }
-        try {
-            if (DBG) log("notifyPortalCheckComplete: ni=" + info);
-            mConnService.captivePortalCheckComplete(info);
-        } catch(RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
     private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
         if (info == null) {
             loge("notifyPortalCheckComplete on null");
@@ -464,7 +433,6 @@
                 latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
                 List<CellInfo> info = mTelephonyManager.getAllCellInfo();
                 if (info == null) return;
-                StringBuffer uniqueCellId = new StringBuffer();
                 int numRegisteredCellInfo = 0;
                 for (CellInfo cellInfo : info) {
                     if (cellInfo.isRegistered()) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c78a973..0f8dc7a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1330,24 +1330,6 @@
 
     /**
      * Signal that the captive portal check on the indicated network
-     * is complete and we can turn the network on for general use.
-     *
-     * @param info the {@link NetworkInfo} object for the networkType
-     *        in question.
-     *
-     * <p>This method requires the call to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
-     * {@hide}
-     */
-    public void captivePortalCheckComplete(NetworkInfo info) {
-        try {
-            mService.captivePortalCheckComplete(info);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Signal that the captive portal check on the indicated network
      * is complete and whether its a captive portal or not.
      *
      * @param info the {@link NetworkInfo} object for the networkType
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index 51a1191..a5d059e 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -117,11 +117,6 @@
     }
 
     @Override
-    public void captivePortalCheckComplete() {
-        // not implemented
-    }
-
-    @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         // not implemented
     }
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 501484c..c7e98c5 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -270,11 +270,6 @@
     }
 
     @Override
-    public void captivePortalCheckComplete() {
-        // not implemented
-    }
-
-    @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         // not implemented
     }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index c1da2e3..b3217eb 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -129,8 +129,6 @@
 
     boolean updateLockdownVpn();
 
-    void captivePortalCheckComplete(in NetworkInfo info);
-
     void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
 
     void supplyMessenger(int networkType, in Messenger messenger);
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index c106514..d9c35c0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -460,11 +460,6 @@
     }
 
     @Override
-    public void captivePortalCheckComplete() {
-        // not implemented
-    }
-
-    @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
             // Captive portal change enable/disable failing fast
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 4d2a70d..53b1308 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -156,18 +156,20 @@
     /** {@hide} */
     public NetworkInfo(NetworkInfo source) {
         if (source != null) {
-            mNetworkType = source.mNetworkType;
-            mSubtype = source.mSubtype;
-            mTypeName = source.mTypeName;
-            mSubtypeName = source.mSubtypeName;
-            mState = source.mState;
-            mDetailedState = source.mDetailedState;
-            mReason = source.mReason;
-            mExtraInfo = source.mExtraInfo;
-            mIsFailover = source.mIsFailover;
-            mIsRoaming = source.mIsRoaming;
-            mIsAvailable = source.mIsAvailable;
-            mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
+            synchronized (source) {
+                mNetworkType = source.mNetworkType;
+                mSubtype = source.mSubtype;
+                mTypeName = source.mTypeName;
+                mSubtypeName = source.mSubtypeName;
+                mState = source.mState;
+                mDetailedState = source.mDetailedState;
+                mReason = source.mReason;
+                mExtraInfo = source.mExtraInfo;
+                mIsFailover = source.mIsFailover;
+                mIsRoaming = source.mIsRoaming;
+                mIsAvailable = source.mIsAvailable;
+                mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
+            }
         }
     }
 
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 1ca9255..c49b1d1 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -144,11 +144,6 @@
     public boolean reconnect();
 
     /**
-     * Ready to switch on to the network after captive portal check
-     */
-    public void captivePortalCheckComplete();
-
-    /**
      * Captive portal check has completed
      */
     public void captivePortalCheckCompleted(boolean isCaptive);
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index 648a4b3..78ac75f 100644
--- a/core/java/android/net/ProxyProperties.java
+++ b/core/java/android/net/ProxyProperties.java
@@ -178,7 +178,7 @@
         // If PAC URL is present in either then they must be equal.
         // Other parameters will only be for fall back.
         if (!TextUtils.isEmpty(mPacFileUrl)) {
-            return mPacFileUrl.equals(p.getPacFileUrl());
+            return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort;
         }
         if (!TextUtils.isEmpty(p.getPacFileUrl())) {
             return false;
@@ -219,6 +219,7 @@
         if (mPacFileUrl != null) {
             dest.writeByte((byte)1);
             dest.writeString(mPacFileUrl);
+            dest.writeInt(mPort);
             return;
         } else {
             dest.writeByte((byte)0);
@@ -244,7 +245,9 @@
                 String host = null;
                 int port = 0;
                 if (in.readByte() != 0) {
-                    return new ProxyProperties(in.readString());
+                    String url = in.readString();
+                    int localPort = in.readInt();
+                    return new ProxyProperties(url, localPort);
                 }
                 if (in.readByte() != 0) {
                     host = in.readString();
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 486e75a..6743c6c 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -49,6 +49,8 @@
  * <h3>Developer Guides</h3>
  * <p>For more information about using NFC, read the
  * <a href="{@docRoot}guide/topics/nfc/index.html">Near Field Communication</a> developer guide.</p>
+ * <p>To perform basic file sharing between devices, read
+ * <a href="{@docRoot}training/beam-files/index.html">Sharing Files with NFC</a>.
  * </div>
  */
 public final class NfcAdapter {
@@ -309,8 +311,12 @@
     final Context mContext;
 
     /**
-     * A callback to be invoked when the system has found a tag in
-     * reader mode.
+     * A callback to be invoked when the system finds a tag while the foreground activity is
+     * operating in reader mode.
+     * <p>Register your {@code ReaderCallback} implementation with {@link
+     * NfcAdapter#enableReaderMode} and disable it with {@link
+     * NfcAdapter#disableReaderMode}.
+     * @see NfcAdapter#enableReaderMode
      */
     public interface ReaderCallback {
         public void onTagDiscovered(Tag tag);
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 3cd7863..58d9616 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -33,6 +33,18 @@
 import java.util.HashMap;
 import java.util.List;
 
+/**
+ * This class can be used to query the state of
+ * NFC card emulation services.
+ *
+ * For a general introduction into NFC card emulation,
+ * please read the <a href="{@docRoot}guide/topics/nfc/ce.html">
+ * NFC card emulation developer guide</a>.</p>
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ */
 public final class CardEmulation {
     static final String TAG = "CardEmulation";
 
@@ -50,32 +62,28 @@
             "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
 
     /**
-     * The category extra for {@link #ACTION_CHANGE_DEFAULT}
+     * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
      *
      * @see #ACTION_CHANGE_DEFAULT
      */
     public static final String EXTRA_CATEGORY = "category";
 
     /**
-     * The ComponentName object passed in as a parcelable
-     * extra for {@link #ACTION_CHANGE_DEFAULT}
+     * The service {@link ComponentName} object passed in as an
+     * extra for {@link #ACTION_CHANGE_DEFAULT}.
      *
      * @see #ACTION_CHANGE_DEFAULT
      */
     public static final String EXTRA_SERVICE_COMPONENT = "component";
 
     /**
-     * The payment category can be used to indicate that an AID
-     * represents a payment application.
+     * Category used for NFC payment services.
      */
     public static final String CATEGORY_PAYMENT = "payment";
 
     /**
-     * If an AID group does not contain a category, or the
-     * specified category is not defined by the platform version
-     * that is parsing the AID group, all AIDs in the group will
-     * automatically be categorized under the {@link #CATEGORY_OTHER}
-     * category.
+     * Category that can be used for all other card emulation
+     * services.
      */
     public static final String CATEGORY_OTHER = "other";
 
@@ -83,49 +91,23 @@
      * Return value for {@link #getSelectionModeForCategory(String)}.
      *
      * <p>In this mode, the user has set a default service for this
-     *    AID category. If a remote reader selects any of the AIDs
+     *    category.
+     *
+     * <p>When using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, if a remote NFC device selects
+     *    any of the Application IDs (AIDs)
      *    that the default service has registered in this category,
      *    that service will automatically be bound to to handle
      *    the transaction.
-     *
-     * <p>There are still cases where a service that is
-     *    not the default for a category can selected:
-     *    <p>
-     *    If a remote reader selects an AID in this category
-     *    that is not handled by the default service, and there is a set
-     *    of other services {S} that do handle this AID, the
-     *    user is asked if he wants to use any of the services in
-     *    {S} instead.
-     *    <p>
-     *    As a special case, if the size of {S} is one, containing a single service X,
-     *    and all AIDs X has registered in this category are not
-     *    registered by any other service, then X will be
-     *    selected automatically without asking the user.
-     *    <p>Example:
-     *    <ul>
-     *    <li>Service A registers AIDs "1", "2" and "3" in the category
-     *    <li>Service B registers AIDs "3" and "4" in the category
-     *    <li>Service C registers AIDs "5" and "6" in the category
-     *    </ul>
-     *    In this case, the following will happen when service A
-     *    is the default:
-     *    <ul>
-     *    <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
-     *    <li>Reader selects AID "4": the user is asked to confirm he
-     *        wants to use service B, because its AIDs overlap with service A.
-     *    <li>Reader selects AID "5" or "6": service C is invoked automatically,
-     *        because all AIDs it has asked for are only registered by C,
-     *        and there is no overlap.
-     *    </ul>
-     *
      */
     public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
 
     /**
      * Return value for {@link #getSelectionModeForCategory(String)}.
      *
-     * <p>In this mode, whenever an AID of this category is selected,
-     *    the user is asked which service he wants to use to handle
+     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
+     *    is selected, the user is asked which service he wants to use to handle
      *    the transaction, even if there is only one matching service.
      */
     public static final int SELECTION_MODE_ALWAYS_ASK = 1;
@@ -133,13 +115,16 @@
     /**
      * Return value for {@link #getSelectionModeForCategory(String)}.
      *
-     * <p>In this mode, the user will only be asked to select a service
-     *    if the selected AID has been registered by multiple applications.
+     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
+     *    or {@link OffHostApduService}, the user will only be asked to select a service
+     *    if the Application ID (AID) selected by the reader has been registered by multiple
+     *    services. If there is only one service that has registered for the AID,
+     *    that service will be invoked directly.
      */
     public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
 
     static boolean sIsInitialized = false;
-    static HashMap<Context, CardEmulation> sCardEmus = new HashMap();
+    static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
     static INfcCardEmulation sService;
 
     final Context mContext;
@@ -149,6 +134,12 @@
         sService = service;
     }
 
+    /**
+     * Helper to get an instance of this class.
+     *
+     * @param adapter A reference to an NfcAdapter object.
+     * @return
+     */
     public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
         if (adapter == null) throw new NullPointerException("NfcAdapter is null");
         Context context = adapter.getContext();
@@ -188,12 +179,19 @@
      * the default service to handle a card emulation category.
      *
      * <p>Note that if {@link #getSelectionModeForCategory(String)}
-     * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
-     * return false.
+     * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
+     * this method will always return false. That is because in these
+     * selection modes a default can't be set at the category level. For categories where
+     * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
+     * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
+     * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
+     * is the default for a specific AID.
      *
      * @param service The ComponentName of the service
      * @param category The category
      * @return whether service is currently the default service for the category.
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      */
     public boolean isDefaultServiceForCategory(ComponentName service, String category) {
         try {
@@ -222,7 +220,9 @@
      *
      * @param service The ComponentName of the service
      * @param aid The ISO7816-4 Application ID
-     * @return
+     * @return whether the service is the default handler for the specified AID
+     *
+     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
      */
     public boolean isDefaultServiceForAid(ComponentName service, String aid) {
         try {
@@ -244,16 +244,16 @@
     }
 
     /**
-     * Returns the application selection mode for the passed in category.
+     * Returns the service selection mode for the passed in category.
      * Valid return values are:
      * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
-     *    application for this category, which will be preferred.
+     *    service for this category, which will be preferred.
      * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
-     *    every time what app he would like to use in this category.
+     *    every time what service he would like to use in this category.
      * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
      *    to pick a service if there is a conflict.
      * @param category The category, for example {@link #CATEGORY_PAYMENT}
-     * @return
+     * @return the selection mode for the passed in category
      */
     public int getSelectionModeForCategory(String category) {
         if (CATEGORY_PAYMENT.equals(category)) {
@@ -314,6 +314,7 @@
             }
         }
     }
+
     /**
      * @hide
      */
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index e2c3ca6..ad34e61 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -4,6 +4,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Service;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -13,30 +14,136 @@
 import android.util.Log;
 
 /**
- * <p>A convenience class that can be extended to implement
- * a service that processes ISO7816-4 commands on top of
- * the ISO14443-4 / IsoDep protocol (T=CL).
+ * <p>HostApduService is a convenience {@link Service} class that can be
+ * extended to emulate an NFC card inside an Android
+ * service component.
  *
- * <p>To tell the platform which ISO7816 application ID (AIDs)
- * are implemented by this service, a {@link #SERVICE_META_DATA}
+ * <div class="special reference">
+ * <h3>Developer Guide</h3>
+ * For a general introduction into the topic of card emulation,
+ * please read the <a href="{@docRoot}guide/topics/nfc/ce.html">
+ * NFC card emulation developer guide.</a></p>
+ * </div>
+ *
+ * <h3>NFC Protocols</h3>
+ * <p>Cards emulated by this class are based on the NFC-Forum ISO-DEP
+ * protocol (based on ISO/IEC 14443-4) and support processing
+ * command Application Protocol Data Units (APDUs) as
+ * defined in the ISO/IEC 7816-4 specification.
+ *
+ * <h3>Service selection</h3>
+ * <p>When a remote NFC device wants to talk to your
+ * service, it sends a so-called
+ * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification.
+ * The AID is an application identifier defined in ISO/IEC 7816-4.
+ *
+ * <p>The registration procedure for AIDs is defined in the
+ * ISO/IEC 7816-5 specification. If you don't want to register an
+ * AID, you are free to use AIDs in the proprietary range:
+ * bits 8-5 of the first byte must each be set to '1'. For example,
+ * "0xF00102030405" is a proprietary AID. If you do use proprietary
+ * AIDs, it is recommended to choose an AID of at least 6 bytes,
+ * to reduce the risk of collisions with other applications that
+ * might be using proprietary AIDs as well.
+ *
+ * <h3>AID groups</h3>
+ * <p>In some cases, a service may need to register multiple AIDs
+ * to implement a certain application, and it needs to be sure
+ * that it is the default handler for all of these AIDs (as opposed
+ * to some AIDs in the group going to another service).
+ *
+ * <p>An AID group is a list of AIDs that should be considered as
+ * belonging together by the OS. For all AIDs in an AID group, the
+ * OS will guarantee one of the following:
+ * <ul>
+ * <li>All AIDs in the group are routed to this service
+ * <li>No AIDs in the group are routed to this service
+ * </ul>
+ * In other words, there is no in-between state, where some AIDs
+ * in the group can be routed to this service, and some to another.
+ * <h3>AID groups and categories</h3>
+ * <p>Each AID group can be associated with a category. This allows
+ * the Android OS to classify services, and it allows the user to
+ * set defaults at the category level instead of the AID level.
+ *
+ * <p>You can use
+ * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)}
+ * to determine if your service is the default handler for a category.
+ *
+ * <p>In this version of the platform, the only known categories
+ * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}.
+ * AID groups without a category, or with a category that is not recognized
+ * by the current platform version, will automatically be
+ * grouped into the {@link CardEmulation#CATEGORY_OTHER} category.
+ * <h3>Service AID registration</h3>
+ * <p>To tell the platform which AIDs groups
+ * are requested by this service, a {@link #SERVICE_META_DATA}
  * entry must be included in the declaration of the service. An
- * example of such a service declaration is shown below:
- * <pre> &lt;service android:name=".MyHostApduService"&gt;
+ * example of a HostApduService manifest declaration is shown below:
+ * <pre> &lt;service android:name=".MyHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"&gt;
  *     &lt;intent-filter&gt;
- *         &lt;action android:name="android.nfc.HostApduService"/&gt;
+ *         &lt;action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/&gt;
  *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.HostApduService" android:resource="@xml/apduservice.xml"/&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
  * &lt;/service&gt;</pre>
- * <p>For more details refer to {@link #SERVICE_META_DATA},
- * <code>&lt;{@link android.R.styleable#HostApduService host-apdu-service}&gt;</code>,
- * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
- * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
- * <p class="note">The Android platform currently only supports a single
- * logical channel.
+ *
+ * This meta-data tag points to an apduservice.xml file.
+ * An example of this file with a single AID group declaration is shown below:
+ * <pre>
+ * &lt;host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ *           android:description="@string/servicedesc" android:requireDeviceUnlock="false"&gt;
+ *       &lt;aid-group android:description="@string/aiddescription" android:category="other">
+ *           &lt;aid-filter android:name="F0010203040506"/&gt;
+ *           &lt;aid-filter android:name="F0394148148100"/&gt;
+ *       &lt;/aid-group&gt;
+ * &lt;/host-apdu-service&gt;
+ * </pre>
+ *
+ * <p>The {@link android.R.styleable#HostApduService &lt;host-apdu-service&gt;} is required
+ * to contain a
+ * {@link android.R.styleable#HostApduService_description &lt;android:description&gt;}
+ * attribute that contains a user-friendly description of the service that may be shown in UI.
+ * The
+ * {@link android.R.styleable#HostApduService_requireDeviceUnlock &lt;requireDeviceUnlock&gt;}
+ * attribute can be used to specify that the device must be unlocked before this service
+ * can be invoked to handle APDUs.
+ * <p>The {@link android.R.styleable#HostApduService &lt;host-apdu-service&gt;} must
+ * contain one or more {@link android.R.styleable#AidGroup &lt;aid-group&gt;} tags.
+ * Each {@link android.R.styleable#AidGroup &lt;aid-group&gt;} must contain one or
+ * more {@link android.R.styleable#AidFilter &lt;aid-filter&gt;} tags, each of which
+ * contains a single AID. The AID must be specified in hexadecimal format, and contain
+ * an even number of characters.
+ * <h3>AID conflict resolution</h3>
+ * Multiple HostApduServices may be installed on a single device, and the same AID
+ * can be registered by more than one service. The Android platform resolves AID
+ * conflicts depending on which category an AID belongs to. Each category may
+ * have a different conflict resolution policy. For example, for some categories
+ * the user may be able to select a default service in the Android settings UI.
+ * For other categories, to policy may be to always ask the user which service
+ * is to be invoked in case of conflict.
+ *
+ * To query the conflict resolution policy for a certain category, see
+ * {@link CardEmulation#getSelectionModeForCategory(String)}.
+ *
+ * <h3>Data exchange</h3>
+ * <p>Once the platform has resolved a "SELECT AID" command APDU to a specific
+ * service component, the "SELECT AID" command APDU and all subsequent
+ * command APDUs will be sent to that service through
+ * {@link #processCommandApdu(byte[], Bundle)}, until either:
+ * <ul>
+ * <li>The NFC link is broken</li>
+ * <li>A "SELECT AID" APDU is received which resolves to another service</li>
+ * </ul>
+ * These two scenarios are indicated by a call to {@link #onDeactivated(int)}.
+ *
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
+ *
  */
 public abstract class HostApduService extends Service {
     /**
-     * The {@link Intent} that must be declared as handled by the service.
+     * The {@link Intent} action that must be declared as handled by the service.
      */
     @SdkConstant(SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
@@ -260,7 +367,7 @@
      * If you cannot return a response APDU immediately, return null
      * and use the {@link #sendResponseApdu(byte[])} method later.
      *
-     * @param commandApdu The APDU that received from the remote device
+     * @param commandApdu The APDU that was received from the remote device
      * @param extras A bundle containing extra data. May be null.
      * @return a byte-array containing the response APDU, or null if no
      *         response APDU can be sent at this point.
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java
index 15f63f9..0d01762 100644
--- a/core/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/core/java/android/nfc/cardemulation/OffHostApduService.java
@@ -4,41 +4,126 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Service;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.IBinder;
 
 /**
- * <p>A convenience class that can be extended to implement
- * a service that registers ISO7814-4 AIDs that reside off-host,
- * for example on an embedded secure element or UICC.
+ * <p>OffHostApduService is a convenience {@link Service} class that can be
+ * extended to describe one or more NFC applications that are residing
+ * off-host, for example on an embedded secure element or a UICC.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guide</h3>
+ * For a general introduction into the topic of card emulation,
+ * please read the <a href="{@docRoot}guide/topics/nfc/ce.html">
+ * NFC card emulation developer guide.</a></p>
+ * </div>
+ *
+ * <h3>NFC Protocols</h3>
+ * <p>Off-host applications represented by this class are based on the NFC-Forum ISO-DEP
+ * protocol (based on ISO/IEC 14443-4) and support processing
+ * command Application Protocol Data Units (APDUs) as
+ * defined in the ISO/IEC 7816-4 specification.
+ *
+ * <h3>Service selection</h3>
+ * <p>When a remote NFC device wants to talk to your
+ * off-host NFC application, it sends a so-called
+ * "SELECT AID" APDU as defined in the ISO/IEC 7816-4 specification.
+ * The AID is an application identifier defined in ISO/IEC 7816-4.
+ *
+ * <p>The registration procedure for AIDs is defined in the
+ * ISO/IEC 7816-5 specification. If you don't want to register an
+ * AID, you are free to use AIDs in the proprietary range:
+ * bits 8-5 of the first byte must each be set to '1'. For example,
+ * "0xF00102030405" is a proprietary AID. If you do use proprietary
+ * AIDs, it is recommended to choose an AID of at least 6 bytes,
+ * to reduce the risk of collisions with other applications that
+ * might be using proprietary AIDs as well.
+ *
+ * <h3>AID groups</h3>
+ * <p>In some cases, an off-host environment may need to register multiple AIDs
+ * to implement a certain application, and it needs to be sure
+ * that it is the default handler for all of these AIDs (as opposed
+ * to some AIDs in the group going to another service).
+ *
+ * <p>An AID group is a list of AIDs that should be considered as
+ * belonging together by the OS. For all AIDs in an AID group, the
+ * OS will guarantee one of the following:
+ * <ul>
+ * <li>All AIDs in the group are routed to the off-host execution environment
+ * <li>No AIDs in the group are routed to the off-host execution environment
+ * </ul>
+ * In other words, there is no in-between state, where some AIDs
+ * in the group can be routed to this off-host execution environment,
+ * and some to another or a host-based {@link HostApduService}.
+ * <h3>AID groups and categories</h3>
+ * <p>Each AID group can be associated with a category. This allows
+ * the Android OS to classify services, and it allows the user to
+ * set defaults at the category level instead of the AID level.
+ *
+ * <p>You can use
+ * {@link CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String)}
+ * to determine if your off-host service is the default handler for a category.
+ *
+ * <p>In this version of the platform, the only known categories
+ * are {@link CardEmulation#CATEGORY_PAYMENT} and {@link CardEmulation#CATEGORY_OTHER}.
+ * AID groups without a category, or with a category that is not recognized
+ * by the current platform version, will automatically be
+ * grouped into the {@link CardEmulation#CATEGORY_OTHER} category.
+ *
+ * <h3>Service AID registration</h3>
+ * <p>To tell the platform which AIDs
+ * reside off-host and are managed by this service, a {@link #SERVICE_META_DATA}
+ * entry must be included in the declaration of the service. An
+ * example of a OffHostApduService manifest declaration is shown below:
+ * <pre> &lt;service android:name=".MyOffHostApduService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"&gt;
+ *     &lt;intent-filter&gt;
+ *         &lt;action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/&gt;
+ *     &lt;/intent-filter&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ * &lt;/service&gt;</pre>
+ *
+ * This meta-data tag points to an apduservice.xml file.
+ * An example of this file with a single AID group declaration is shown below:
+ * <pre>
+ * &lt;offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ *           android:description="@string/servicedesc"&gt;
+ *       &lt;aid-group android:description="@string/subscription" android:category="other">
+ *           &lt;aid-filter android:name="F0010203040506"/&gt;
+ *           &lt;aid-filter android:name="F0394148148100"/&gt;
+ *       &lt;/aid-group&gt;
+ * &lt;/offhost-apdu-service&gt;
+ * </pre>
+ *
+ * <p>The {@link android.R.styleable#OffHostApduService &lt;offhost-apdu-service&gt;} is required
+ * to contain a
+ * {@link android.R.styleable#OffHostApduService_description &lt;android:description&gt;}
+ * attribute that contains a user-friendly description of the service that may be shown in UI.
+ *
+ * <p>The {@link android.R.styleable#OffHostApduService &lt;offhost-apdu-service&gt;} must
+ * contain one or more {@link android.R.styleable#AidGroup &lt;aid-group&gt;} tags.
+ * Each {@link android.R.styleable#AidGroup &lt;aid-group&gt;} must contain one or
+ * more {@link android.R.styleable#AidFilter &lt;aid-filter&gt;} tags, each of which
+ * contains a single AID. The AID must be specified in hexadecimal format, and contain
+ * an even number of characters.
  *
  * <p>This registration will allow the service to be included
- * as an option for handling these AIDs on non-host execution
- * environments. The Operating System will take care of correctly
- * routing the AIDs, based on which service the user has selected
- * to be the handler for an AID.
+ * as an option for being the default handler for categories.
+ * The Android OS will take care of correctly
+ * routing the AIDs to the off-host execution environment,
+ * based on which service the user has selected to be the handler for a certain category.
  *
  * <p>The service may define additional actions outside of the
  * Android namespace that provide further interaction with
  * the off-host execution environment.
  *
- * <p>To tell the platform which ISO7816 application ID (AIDs)
- * are present and handled by the app containing this service,
- * a {@link #SERVICE_META_DATA} entry must be included in the declaration
- * of the service. An example of such a service declaration is shown below:
- * <pre> &lt;service android:name=".MyOffHostApduService"&gt;
- *     &lt;intent-filter&gt;
- *         &lt;action android:name="android.nfc.OffHostApduService"/&gt;
- *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.OffHostApduService" android:resource="@xml/apduservice.xml"/&gt;
- * &lt;/service&gt;</pre>
- * <p>For more details refer to {@link #SERVICE_META_DATA},
- * <code>&lt;{@link android.R.styleable#OffHostApduService offhost-apdu-service}&gt;</code>,
- * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
- * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
+ * <p class="note">Use of this class requires the
+ * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
+ * on the device.
  */
 public abstract class OffHostApduService extends Service {
     /**
-     * The {@link Intent} that must be declared as handled by the service.
+     * The {@link Intent} action that must be declared as handled by the service.
      */
     @SdkConstant(SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/core/java/android/nfc/tech/NfcBarcode.java
index 76627de..8901f28 100644
--- a/core/java/android/nfc/tech/NfcBarcode.java
+++ b/core/java/android/nfc/tech/NfcBarcode.java
@@ -102,15 +102,21 @@
      *       </ul>
      *     <p>The following 12 bytes are payload:<ul>
      *       <li> In case of a URL payload, the payload is encoded in US-ASCII,
-     *            following the limitations defined in RF3987,
-     *            {@see http://www.ietf.org/rfc/rfc3987.txt}</li>
-     *       <li> In case of GS1 EPC daya, {@see http://www.gs1.org/gsmp/kc/epcglobal/tds/}
-     *            for more details.</li></ul>
+     *            following the limitations defined in RFC3987.
+     *            {@see <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>}</li>
+     *       <li> In case of GS1 EPC data, see <a href="http://www.gs1.org/gsmp/kc/epcglobal/tds/">
+     *            GS1 Electronic Product Code (EPC) Tag Data Standard (TDS)</a> for more details.
+     *       </li>
+     *     </ul>
      *     <p>The last 2 bytes comprise the CRC.
      *     </ul>
      * <p>Does not cause any RF activity and does not block.
      *
      * @return a byte array containing the barcode
+     * @see <a href="http://www.kovio.com/docs/kovionfcbarcode.pdf">
+     *      Kovio 128-bit NFC barcode datasheet</a>
+     * @see <a href="http://kovio.com/docs/kovio-128-nfc-barcode-data-format.pdf">
+     *      Kovio 128-bit NFC barcode data format</a>
      */
     public byte[] getBarcode() {
         switch (mType) {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index db5cf1c..b5413db 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -686,7 +686,8 @@
      *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
      */
     public static String getExternalStorageState() {
-        return getStorageState(getExternalStorageDirectory());
+        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        return getStorageState(externalDir);
     }
 
     /**
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 4d48fd4..ff3e277 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -144,13 +144,6 @@
         }
     }
 
-    /** returns the FAT file system volume ID for the volume mounted 
-     * at the given mount point, or -1 for failure
-     * @param mountPoint point for FAT volume
-     * @return volume ID or -1
-     */
-    public static native int getFatVolumeId(String mountPoint);
-
     /**
      * Perform an fsync on the given FileOutputStream.  The stream at this
      * point must be flushed but not yet closed.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 4c7bbb4..56176a4 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -23,11 +23,12 @@
 
 interface IPowerManager
 {
-    // WARNING: The first three methods must remain the first three methods because their
+    // WARNING: The first four methods must remain the first three methods because their
     // transaction numbers must not change unless IPowerManager.cpp is also updated.
     void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws);
     void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, int uidtoblame);
     void releaseWakeLock(IBinder lock, int flags);
+    void updateWakeLockUids(IBinder lock, in int[] uids);
 
     void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
     boolean isWakeLockLevelSupported(int level);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 78c859e..21e9f6b 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -59,7 +59,6 @@
 
     final MessageQueue mQueue;
     final Thread mThread;
-    volatile boolean mRun;
 
     private Printer mLogging;
 
@@ -187,7 +186,6 @@
 
     private Looper(boolean quitAllowed) {
         mQueue = new MessageQueue(quitAllowed);
-        mRun = true;
         mThread = Thread.currentThread();
     }
 
@@ -300,27 +298,12 @@
     }
 
     public void dump(Printer pw, String prefix) {
-        pw = PrefixPrinter.create(pw, prefix);
-        pw.println(this.toString());
-        pw.println("mRun=" + mRun);
-        pw.println("mThread=" + mThread);
-        pw.println("mQueue=" + ((mQueue != null) ? mQueue : "(null"));
-        if (mQueue != null) {
-            synchronized (mQueue) {
-                long now = SystemClock.uptimeMillis();
-                Message msg = mQueue.mMessages;
-                int n = 0;
-                while (msg != null) {
-                    pw.println("  Message " + n + ": " + msg.toString(now));
-                    n++;
-                    msg = msg.next;
-                }
-                pw.println("(Total messages: " + n + ")");
-            }
-        }
+        pw.println(prefix + toString());
+        mQueue.dump(pw, prefix + "  ");
     }
 
     public String toString() {
-        return "Looper{" + Integer.toHexString(System.identityHashCode(this)) + "}";
+        return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+                + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
     }
 }
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 0abc149..51203a48 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -428,36 +428,48 @@
     public Message() {
     }
 
+    @Override
     public String toString() {
         return toString(SystemClock.uptimeMillis());
     }
 
     String toString(long now) {
-        StringBuilder   b = new StringBuilder();
-        
-        b.append("{ what=");
-        b.append(what);
+        StringBuilder b = new StringBuilder();
+        b.append("{ when=");
+        TimeUtils.formatDuration(when - now, b);
 
-        b.append(" when=");
-        TimeUtils.formatDuration(when-now, b);
+        if (target != null) {
+            if (callback != null) {
+                b.append(" callback=");
+                b.append(callback.getClass().getName());
+            } else {
+                b.append(" what=");
+                b.append(what);
+            }
 
-        if (arg1 != 0) {
-            b.append(" arg1=");
+            if (arg1 != 0) {
+                b.append(" arg1=");
+                b.append(arg1);
+            }
+
+            if (arg2 != 0) {
+                b.append(" arg2=");
+                b.append(arg2);
+            }
+
+            if (obj != null) {
+                b.append(" obj=");
+                b.append(obj);
+            }
+
+            b.append(" target=");
+            b.append(target.getClass().getName());
+        } else {
+            b.append(" barrier=");
             b.append(arg1);
         }
 
-        if (arg2 != 0) {
-            b.append(" arg2=");
-            b.append(arg2);
-        }
-
-        if (obj != null) {
-            b.append(" obj=");
-            b.append(obj);
-        }
-
         b.append(" }");
-        
         return b.toString();
     }
 
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 159b194..e90a457 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -18,6 +18,7 @@
 
 import android.util.AndroidRuntimeException;
 import android.util.Log;
+import android.util.Printer;
 
 import java.util.ArrayList;
 
@@ -258,6 +259,7 @@
         synchronized (this) {
             final int token = mNextBarrierToken++;
             final Message msg = Message.obtain();
+            msg.when = when;
             msg.arg1 = token;
 
             Message prev = null;
@@ -399,12 +401,16 @@
 
     boolean isIdling() {
         synchronized (this) {
-            // If the loop is quitting then it must not be idling.
-            // We can assume mPtr != 0 when mQuitting is false.
-            return !mQuitting && nativeIsIdling(mPtr);
+            return isIdlingLocked();
         }
     }
 
+    private boolean isIdlingLocked() {
+        // If the loop is quitting then it must not be idling.
+        // We can assume mPtr != 0 when mQuitting is false.
+        return !mQuitting && nativeIsIdling(mPtr);
+     }
+
     void removeMessages(Handler h, int what, Object object) {
         if (h == null) {
             return;
@@ -543,4 +549,17 @@
             }
         }
     }
+
+    void dump(Printer pw, String prefix) {
+        synchronized (this) {
+            long now = SystemClock.uptimeMillis();
+            int n = 0;
+            for (Message msg = mMessages; msg != null; msg = msg.next) {
+                pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+                n++;
+            }
+            pw.println(prefix + "(Total messages: " + n + ", idling=" + isIdlingLocked()
+                    + ", quitting=" + mQuitting + ")");
+        }
+    }
 }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8b293ac..a00345b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -192,6 +192,7 @@
      * @return a new ParcelFileDescriptor pointing to the given file.
      * @throws FileNotFoundException if the given file does not exist or can not
      *             be opened with the requested mode.
+     * @see #parseMode(String)
      */
     public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
         final FileDescriptor fd = openInternal(file, mode);
@@ -216,6 +217,7 @@
      * @return a new ParcelFileDescriptor pointing to the given file.
      * @throws FileNotFoundException if the given file does not exist or can not
      *             be opened with the requested mode.
+     * @see #parseMode(String)
      */
     public static ParcelFileDescriptor open(
             File file, int mode, Handler handler, OnCloseListener listener) throws IOException {
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 5e20dec..b692ffde 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -32,10 +32,8 @@
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.SignatureException;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
-import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -48,7 +46,7 @@
 import org.apache.harmony.security.pkcs7.ContentInfo;
 import org.apache.harmony.security.pkcs7.SignedData;
 import org.apache.harmony.security.pkcs7.SignerInfo;
-import org.apache.harmony.security.provider.cert.X509CertImpl;
+import org.apache.harmony.security.x509.Certificate;
 
 /**
  * RecoverySystem contains methods for interacting with the Android
@@ -93,9 +91,9 @@
     }
 
     /** @return the set of certs that can be used to sign an OTA package. */
-    private static HashSet<Certificate> getTrustedCerts(File keystore)
+    private static HashSet<X509Certificate> getTrustedCerts(File keystore)
         throws IOException, GeneralSecurityException {
-        HashSet<Certificate> trusted = new HashSet<Certificate>();
+        HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
         if (keystore == null) {
             keystore = DEFAULT_KEYSTORE;
         }
@@ -107,7 +105,7 @@
                 ZipEntry entry = entries.nextElement();
                 InputStream is = zip.getInputStream(entry);
                 try {
-                    trusted.add(cf.generateCertificate(is));
+                    trusted.add((X509Certificate) cf.generateCertificate(is));
                 } finally {
                     is.close();
                 }
@@ -201,21 +199,23 @@
             if (signedData == null) {
                 throw new IOException("signedData is null");
             }
-            Collection encCerts = signedData.getCertificates();
+            List<Certificate> encCerts = signedData.getCertificates();
             if (encCerts.isEmpty()) {
                 throw new IOException("encCerts is empty");
             }
             // Take the first certificate from the signature (packages
             // should contain only one).
-            Iterator it = encCerts.iterator();
+            Iterator<Certificate> it = encCerts.iterator();
             X509Certificate cert = null;
             if (it.hasNext()) {
-                cert = new X509CertImpl((org.apache.harmony.security.x509.Certificate)it.next());
+                CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                InputStream is = new ByteArrayInputStream(it.next().getEncoded());
+                cert = (X509Certificate) cf.generateCertificate(is);
             } else {
                 throw new SignatureException("signature contains no certificates");
             }
 
-            List sigInfos = signedData.getSignerInfos();
+            List<SignerInfo> sigInfos = signedData.getSignerInfos();
             SignerInfo sigInfo;
             if (!sigInfos.isEmpty()) {
                 sigInfo = (SignerInfo)sigInfos.get(0);
@@ -226,12 +226,12 @@
             // Check that the public key of the certificate contained
             // in the package equals one of our trusted public keys.
 
-            HashSet<Certificate> trusted = getTrustedCerts(
+            HashSet<X509Certificate> trusted = getTrustedCerts(
                 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
 
             PublicKey signatureKey = cert.getPublicKey();
             boolean verified = false;
-            for (Certificate c : trusted) {
+            for (X509Certificate c : trusted) {
                 if (c.getPublicKey().equals(signatureKey)) {
                     verified = true;
                     break;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 177a955..0285cb9 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -21,6 +21,9 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.CharArrayWriter;
 import java.io.File;
 
 /**
@@ -46,6 +49,10 @@
     /** When set, indicates exclusive ownership of this volume */
     private final UserHandle mOwner;
 
+    private String mUuid;
+    private String mUserLabel;
+    private String mState;
+
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
     // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
@@ -76,6 +83,9 @@
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
+        mUuid = in.readString();
+        mUserLabel = in.readString();
+        mState = in.readString();
     }
 
     public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
@@ -189,6 +199,45 @@
         return mOwner;
     }
 
+    public void setUuid(String uuid) {
+        mUuid = uuid;
+    }
+
+    public String getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
+     * parse or UUID is unknown.
+     */
+    public int getFatVolumeId() {
+        if (mUuid == null || mUuid.length() != 9) {
+            return -1;
+        }
+        try {
+            return Integer.parseInt(mUuid.replace("-", ""), 16);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    public void setUserLabel(String userLabel) {
+        mUserLabel = userLabel;
+    }
+
+    public String getUserLabel() {
+        return mUserLabel;
+    }
+
+    public void setState(String state) {
+        mState = state;
+    }
+
+    public String getState() {
+        return mState;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -205,19 +254,28 @@
 
     @Override
     public String toString() {
-        final StringBuilder builder = new StringBuilder("StorageVolume [");
-        builder.append("mStorageId=").append(mStorageId);
-        builder.append(" mPath=").append(mPath);
-        builder.append(" mDescriptionId=").append(mDescriptionId);
-        builder.append(" mPrimary=").append(mPrimary);
-        builder.append(" mRemovable=").append(mRemovable);
-        builder.append(" mEmulated=").append(mEmulated);
-        builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace);
-        builder.append(" mAllowMassStorage=").append(mAllowMassStorage);
-        builder.append(" mMaxFileSize=").append(mMaxFileSize);
-        builder.append(" mOwner=").append(mOwner);
-        builder.append("]");
-        return builder.toString();
+        final CharArrayWriter writer = new CharArrayWriter();
+        dump(new IndentingPrintWriter(writer, "    ", 80));
+        return writer.toString();
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("StorageVolume:");
+        pw.increaseIndent();
+        pw.printPair("mStorageId", mStorageId);
+        pw.printPair("mPath", mPath);
+        pw.printPair("mDescriptionId", mDescriptionId);
+        pw.printPair("mPrimary", mPrimary);
+        pw.printPair("mRemovable", mRemovable);
+        pw.printPair("mEmulated", mEmulated);
+        pw.printPair("mMtpReserveSpace", mMtpReserveSpace);
+        pw.printPair("mAllowMassStorage", mAllowMassStorage);
+        pw.printPair("mMaxFileSize", mMaxFileSize);
+        pw.printPair("mOwner", mOwner);
+        pw.printPair("mUuid", mUuid);
+        pw.printPair("mUserLabel", mUserLabel);
+        pw.printPair("mState", mState);
+        pw.decreaseIndent();
     }
 
     public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
@@ -249,5 +307,8 @@
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
+        parcel.writeString(mUuid);
+        parcel.writeString(mUserLabel);
+        parcel.writeString(mState);
     }
 }
diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl
index b12c922..9d384fb 100644
--- a/core/java/android/print/IPrintDocumentAdapter.aidl
+++ b/core/java/android/print/IPrintDocumentAdapter.aidl
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.print.ILayoutResultCallback;
+import android.print.IPrintDocumentAdapterObserver;
 import android.print.IWriteResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -29,6 +30,7 @@
  * @hide
  */
 oneway interface IPrintDocumentAdapter {
+    void setObserver(in IPrintDocumentAdapterObserver observer);
     void start();
     void layout(in PrintAttributes oldAttributes, in PrintAttributes newAttributes,
             ILayoutResultCallback callback, in Bundle metadata, int sequence);
diff --git a/tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java b/core/java/android/print/IPrintDocumentAdapterObserver.aidl
similarity index 61%
rename from tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java
rename to core/java/android/print/IPrintDocumentAdapterObserver.aidl
index 7bb4c35..4443df0 100644
--- a/tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java
+++ b/core/java/android/print/IPrintDocumentAdapterObserver.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open 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,18 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.testplugin;
+package android.print;
 
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class TestPlugin extends Service {
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
+/**
+ * Interface for observing the state of a print document adapter.
+ *
+ * @hide
+ */
+oneway interface IPrintDocumentAdapterObserver {
+    void onDestroy();
 }
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 3bd515b..8fa7ab9 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -16,9 +16,9 @@
 
 package android.print;
 
+import android.os.Bundle;
 import android.print.IPrinterDiscoveryObserver;
 import android.print.IPrintDocumentAdapter;
-import android.print.IPrintClient;
 import android.print.PrintJobId;
 import android.print.IPrintJobStateChangeListener;
 import android.print.PrinterId;
@@ -34,9 +34,8 @@
 interface IPrintManager {
     List<PrintJobInfo> getPrintJobInfos(int appId, int userId);
     PrintJobInfo getPrintJobInfo(in PrintJobId printJobId, int appId, int userId);
-    PrintJobInfo print(String printJobName, in IPrintClient client,
-            in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
-            int appId, int userId);
+    Bundle print(String printJobName, in IPrintDocumentAdapter printAdapter,
+            in PrintAttributes attributes, String packageName, int appId, int userId);
     void cancelPrintJob(in PrintJobId printJobId, int appId, int userId);
     void restartPrintJob(in PrintJobId printJobId, int appId, int userId);
 
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 96b168d..7b2cf25 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -18,8 +18,6 @@
 
 import android.content.ComponentName;
 import android.os.ParcelFileDescriptor;
-import android.print.IPrintDocumentAdapter;
-import android.print.IPrintClient;
 import android.print.IPrintSpoolerClient;
 import android.print.IPrintSpoolerCallbacks;
 import android.print.PrinterInfo;
@@ -40,12 +38,12 @@
             int state, int appId, int sequence);
     void getPrintJobInfo(in PrintJobId printJobId, IPrintSpoolerCallbacks callback,
             int appId, int sequence);
-    void createPrintJob(in PrintJobInfo printJob, in IPrintClient client,
-            in IPrintDocumentAdapter printAdapter);
+    void createPrintJob(in PrintJobInfo printJob);
     void setPrintJobState(in PrintJobId printJobId, int status, String stateReason,
             IPrintSpoolerCallbacks callback, int sequence);
     void setPrintJobTag(in PrintJobId printJobId, String tag, IPrintSpoolerCallbacks callback,
             int sequence);
     void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
     void setClient(IPrintSpoolerClient client);
+    void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling);
 }
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index f094962..b721ef4 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -193,7 +193,7 @@
         builder.append("name=").append(mName);
         builder.append(", pageCount=").append(mPageCount);
         builder.append(", contentType=").append(contentTyepToString(mContentType));
-        builder.append(", size=").append(mDataSize);
+        builder.append(", dataSize=").append(mDataSize);
         builder.append("}");
         return builder.toString();
     }
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index ccb4f44..c6f0a68 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -48,6 +48,14 @@
     public static final int STATE_ANY_ACTIVE = -3;
 
     /**
+     * Constant for matching any scheduled, i.e. delivered to a print
+     * service, print job state.
+     *
+     * @hide
+     */
+    public static final int STATE_ANY_SCHEDULED = -4;
+
+    /**
      * Print job state: The print job is being created but not yet
      * ready to be printed.
      * <p>
@@ -123,15 +131,12 @@
     /** The name of the printer - internally used */
     private String mPrinterName;
 
-    /** The status of the print job. */
+    /** The state of the print job. */
     private int mState;
 
     /** The id of the app that created the job. */
     private int mAppId;
 
-    /** The id of the user that created the job. */
-    private int mUserId;
-
     /** Optional tag assigned by a print service.*/
     private String mTag;
 
@@ -153,6 +158,9 @@
     /** Information about the printed document. */
     private PrintDocumentInfo mDocumentInfo;
 
+    /** Whether we are trying to cancel this print job. */
+    private boolean mCanceling;
+
     /** @hide*/
     public PrintJobInfo() {
         /* do nothing */
@@ -166,7 +174,6 @@
         mPrinterName = other.mPrinterName;
         mState = other.mState;
         mAppId = other.mAppId;
-        mUserId = other.mUserId;
         mTag = other.mTag;
         mCreationTime = other.mCreationTime;
         mCopies = other.mCopies;
@@ -174,6 +181,7 @@
         mPageRanges = other.mPageRanges;
         mAttributes = other.mAttributes;
         mDocumentInfo = other.mDocumentInfo;
+        mCanceling = other.mCanceling;
     }
 
     private PrintJobInfo(Parcel parcel) {
@@ -183,7 +191,6 @@
         mPrinterName = parcel.readString();
         mState = parcel.readInt();
         mAppId = parcel.readInt();
-        mUserId = parcel.readInt();
         mTag = parcel.readString();
         mCreationTime = parcel.readLong();
         mCopies = parcel.readInt();
@@ -201,6 +208,7 @@
         if (parcel.readInt() == 1) {
             mDocumentInfo = PrintDocumentInfo.CREATOR.createFromParcel(parcel);
         }
+        mCanceling = (parcel.readInt() == 1);
     }
 
     /**
@@ -328,28 +336,6 @@
     }
 
     /**
-     * Gets the owning user id.
-     *
-     * @return The user id.
-     *
-     * @hide
-     */
-    public int getUserId() {
-        return mUserId;
-    }
-
-    /**
-     * Sets the owning user id.
-     *
-     * @param userId The user id.
-     *
-     * @hide
-     */
-    public void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    /**
      * Gets the optional tag assigned by a print service.
      *
      * @return The tag.
@@ -453,7 +439,7 @@
     /**
      * Sets the included pages.
      *
-     * @return The included pages.
+     * @param pageRanges The included pages.
      *
      * @hide
      */
@@ -503,6 +489,28 @@
         mDocumentInfo = info;
     }
 
+    /**
+     * Gets whether this print is being cancelled.
+     *
+     * @return True if the print job is being cancelled.
+     *
+     * @hide
+     */
+    public boolean isCancelling() {
+        return mCanceling;
+    }
+
+    /**
+     * Sets whether this print is being cancelled.
+     *
+     * @param cancelling True if the print job is being cancelled.
+     *
+     * @hide
+     */
+    public void setCancelling(boolean cancelling) {
+        mCanceling = cancelling;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -516,7 +524,6 @@
         parcel.writeString(mPrinterName);
         parcel.writeInt(mState);
         parcel.writeInt(mAppId);
-        parcel.writeInt(mUserId);
         parcel.writeString(mTag);
         parcel.writeLong(mCreationTime);
         parcel.writeInt(mCopies);
@@ -539,6 +546,7 @@
         } else {
             parcel.writeInt(0);
         }
+        parcel.writeInt(mCanceling ? 1 : 0);
     }
 
     @Override
@@ -547,7 +555,7 @@
         builder.append("PrintJobInfo{");
         builder.append("label: ").append(mLabel);
         builder.append(", id: ").append(mId);
-        builder.append(", status: ").append(stateToString(mState));
+        builder.append(", state: ").append(stateToString(mState));
         builder.append(", printer: " + mPrinterId);
         builder.append(", tag: ").append(mTag);
         builder.append(", creationTime: " + mCreationTime);
@@ -556,6 +564,7 @@
                 ? mAttributes.toString() : null));
         builder.append(", documentInfo: " + (mDocumentInfo != null
                 ? mDocumentInfo.toString() : null));
+        builder.append(", cancelling: " + mCanceling);
         builder.append(", pages: " + (mPageRanges != null
                 ? Arrays.toString(mPageRanges) : null));
         builder.append("}");
@@ -574,6 +583,9 @@
             case STATE_STARTED: {
                 return "STATE_STARTED";
             }
+            case STATE_BLOCKED: {
+                return "STATE_BLOCKED";
+            }
             case STATE_FAILED: {
                 return "STATE_FAILED";
             }
@@ -589,6 +601,81 @@
         }
     }
 
+    /**
+     * Builder for creating a {@link PrintJobInfo}.
+     */
+    public static final class Builder {
+        private final PrintJobInfo mPrototype;
+
+        /**
+         * Constructor.
+         *
+         * @param prototype Prototype to use as a starting point.
+         * Can be null.
+         */
+        public Builder(PrintJobInfo prototype) {
+            mPrototype = (prototype != null)
+                    ? new PrintJobInfo(prototype)
+                    : new PrintJobInfo();
+        }
+
+        /**
+         * Sets the number of copies.
+         *
+         * @param copies The number of copies.
+         */
+        public void setCopies(int copies) {
+            mPrototype.mCopies = copies;
+        }
+
+        /**
+         * Sets the print job attributes.
+         *
+         * @param attributes The attributes.
+         */
+        public void setAttributes(PrintAttributes attributes) {
+            mPrototype.mAttributes = attributes;
+        }
+
+        /**
+         * Sets the included pages.
+         *
+         * @param pages The included pages.
+         */
+        public void setPages(PageRange[] pages) {
+            mPrototype.mPageRanges = pages;
+        }
+
+        /**
+         * Puts an advanced (printer specific) option.
+         *
+         * @param key The option key.
+         * @param value The option value.
+         */
+        public void putAdvancedOption(String key, String value) {
+
+        }
+
+        /**
+         * Puts an advanced (printer specific) option.
+         *
+         * @param key The option key.
+         * @param value The option value.
+         */
+        public void putAdvancedOption(String key, int value) {
+
+        }
+
+        /**
+         * Creates a new {@link PrintJobInfo} instance.
+         *
+         * @return The new instance.
+         */
+        public PrintJobInfo build() {
+            return mPrototype;
+        }
+    }
+
     public static final Parcelable.Creator<PrintJobInfo> CREATOR =
             new Creator<PrintJobInfo>() {
         @Override
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 9c7c1fe..955b4d8 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -16,6 +16,8 @@
 
 package android.print;
 
+import android.app.Activity;
+import android.app.Application.ActivityLifecycleCallbacks;
 import android.content.Context;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
@@ -48,6 +50,7 @@
  * <p>
  * To obtain a handle to the print manager do the following:
  * </p>
+ * 
  * <pre>
  * PrintManager printManager =
  *         (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
@@ -59,6 +62,48 @@
 
     private static final boolean DEBUG = false;
 
+    private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
+
+    /**
+     * The action for launching the print dialog activity.
+     *
+     * @hide
+     */
+    public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
+
+    /**
+     * Extra with the intent for starting the print dialog.
+     * <p>
+     * <strong>Type:</strong> {@link android.content.IntentSender}
+     * </p>
+     *
+     * @hide
+     */
+    public static final String EXTRA_PRINT_DIALOG_INTENT =
+            "android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
+
+    /**
+     * Extra with a print job.
+     * <p>
+     * <strong>Type:</strong> {@link android.print.PrintJobInfo}
+     * </p>
+     *
+     * @hide
+     */
+    public static final String EXTRA_PRINT_JOB =
+            "android.print.intent.extra.EXTRA_PRINT_JOB";
+
+    /**
+     * Extra with the print document adapter to be printed.
+     * <p>
+     * <strong>Type:</strong> {@link android.print.IPrintDocumentAdapter}
+     * </p>
+     *
+     * @hide
+     */
+    public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
+            "android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
+
     /** @hide */
     public static final int APP_ID_ANY = -2;
 
@@ -70,8 +115,6 @@
 
     private final int mAppId;
 
-    private final PrintClient mPrintClient;
-
     private final Handler mHandler;
 
     private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
@@ -81,18 +124,17 @@
 
         /**
          * Callback notifying that a print job state changed.
-         *
+         * 
          * @param printJobId The print job id.
          */
-        public void onPrintJobsStateChanged(PrintJobId printJobId);
+        public void onPrintJobStateChanged(PrintJobId printJobId);
     }
 
     /**
      * Creates a new instance.
-     *
+     * 
      * @param context The current context in which to operate.
      * @param service The backing system service.
-     *
      * @hide
      */
     public PrintManager(Context context, IPrintManager service, int userId, int appId) {
@@ -100,18 +142,21 @@
         mService = service;
         mUserId = userId;
         mAppId = appId;
-        mPrintClient = new PrintClient(this);
         mHandler = new Handler(context.getMainLooper(), null, false) {
             @Override
             public void handleMessage(Message message) {
-                SomeArgs args = (SomeArgs) message.obj;
-                Context context = (Context) args.arg1;
-                IntentSender intent = (IntentSender) args.arg2;
-                args.recycle();
-                try {
-                    context.startIntentSender(intent, null, 0, 0, 0);
-                } catch (SendIntentException sie) {
-                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
+                switch (message.what) {
+                    case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
+                        SomeArgs args = (SomeArgs) message.obj;
+                        PrintJobStateChangeListenerWrapper wrapper =
+                                (PrintJobStateChangeListenerWrapper) args.arg1;
+                        PrintJobStateChangeListener listener = wrapper.getListener();
+                        if (listener != null) {
+                            PrintJobId printJobId = (PrintJobId) args.arg2;
+                            listener.onPrintJobStateChanged(printJobId);
+                        }
+                        args.recycle();
+                    } break;
                 }
             }
         };
@@ -119,10 +164,10 @@
 
     /**
      * Creates an instance that can access all print jobs.
-     *
+     * 
      * @param userId The user id for which to get all print jobs.
-     * @return An instance if the caller has the permission to access
-     * all print jobs, null otherwise.
+     * @return An instance if the caller has the permission to access all print
+     *         jobs, null otherwise.
      * @hide
      */
     public PrintManager getGlobalPrintManagerForUser(int userId) {
@@ -140,9 +185,8 @@
 
     /**
      * Adds a listener for observing the state of print jobs.
-     *
+     * 
      * @param listener The listener to add.
-     *
      * @hide
      */
     public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
@@ -151,7 +195,7 @@
                     PrintJobStateChangeListenerWrapper>();
         }
         PrintJobStateChangeListenerWrapper wrappedListener =
-                new PrintJobStateChangeListenerWrapper(listener);
+                new PrintJobStateChangeListenerWrapper(listener, mHandler);
         try {
             mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
             mPrintJobStateChangeListeners.put(listener, wrappedListener);
@@ -162,9 +206,8 @@
 
     /**
      * Removes a listener for observing the state of print jobs.
-     *
+     * 
      * @param listener The listener to remove.
-     *
      * @hide
      */
     public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
@@ -179,6 +222,7 @@
         if (mPrintJobStateChangeListeners.isEmpty()) {
             mPrintJobStateChangeListeners = null;
         }
+        wrappedListener.destroy();
         try {
             mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
         } catch (RemoteException re) {
@@ -188,11 +232,9 @@
 
     /**
      * Gets a print job given its id.
-     *
+     * 
      * @return The print job list.
-     *
      * @see PrintJob
-     *
      * @hide
      */
     public PrintJob getPrintJob(PrintJobId printJobId) {
@@ -209,9 +251,8 @@
 
     /**
      * Gets the print jobs for this application.
-     *
+     * 
      * @return The print job list.
-     *
      * @see PrintJob
      */
     public List<PrintJob> getPrintJobs() {
@@ -249,9 +290,9 @@
     }
 
     /**
-     * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
-     * attributes.
-     *
+     * Creates a print job for printing a {@link PrintDocumentAdapter} with
+     * default print attributes.
+     * 
      * @param printJobName A name for the new print job.
      * @param documentAdapter An adapter that emits the document to print.
      * @param attributes The default print job attributes.
@@ -263,13 +304,23 @@
         if (TextUtils.isEmpty(printJobName)) {
             throw new IllegalArgumentException("priintJobName cannot be empty");
         }
-        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(documentAdapter,
-                mContext.getMainLooper());
+        PrintDocumentAdapterDelegate delegate = new PrintDocumentAdapterDelegate(
+                mContext, documentAdapter);
         try {
-            PrintJobInfo printJob = mService.print(printJobName, mPrintClient, delegate,
-                    attributes, mAppId, mUserId);
-            if (printJob != null) {
-                return new PrintJob(printJob, this);
+            Bundle result = mService.print(printJobName, delegate,
+                    attributes, mContext.getPackageName(), mAppId, mUserId);
+            if (result != null) {
+                PrintJobInfo printJob = result.getParcelable(EXTRA_PRINT_JOB);
+                IntentSender intent = result.getParcelable(EXTRA_PRINT_DIALOG_INTENT);
+                if (printJob == null || intent == null) {
+                    return null;
+                }
+                try {
+                    mContext.startIntentSender(intent, null, 0, 0, 0);
+                    return new PrintJob(printJob, this);
+                } catch (SendIntentException sie) {
+                    Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
+                }
             }
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error creating a print job", re);
@@ -279,9 +330,8 @@
 
     /**
      * Gets the list of enabled print services.
-     *
+     * 
      * @return The enabled service list or an empty list.
-     *
      * @hide
      */
     public List<PrintServiceInfo> getEnabledPrintServices() {
@@ -298,9 +348,8 @@
 
     /**
      * Gets the list of installed print services.
-     *
+     * 
      * @return The installed service list or an empty list.
-     *
      * @hide
      */
     public List<PrintServiceInfo> getInstalledPrintServices() {
@@ -322,36 +371,21 @@
         return new PrinterDiscoverySession(mService, mContext, mUserId);
     }
 
-    private static final class PrintClient extends IPrintClient.Stub {
-
-        private final WeakReference<PrintManager> mWeakPrintManager;
-
-        public PrintClient(PrintManager manager) {
-            mWeakPrintManager = new WeakReference<PrintManager>(manager);
-        }
-
-        @Override
-        public void startPrintJobConfigActivity(IntentSender intent) {
-            PrintManager manager = mWeakPrintManager.get();
-            if (manager != null) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = manager.mContext;
-                args.arg2 = intent;
-                manager.mHandler.obtainMessage(0, args).sendToTarget();
-            }
-        }
-    }
-
-    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub {
+    private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub
+            implements ActivityLifecycleCallbacks {
 
         private final Object mLock = new Object();
 
         private CancellationSignal mLayoutOrWriteCancellation;
 
-        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
+        private Activity mActivity; // Strong reference OK - cleared in finish()
+
+        private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish
 
         private Handler mHandler; // Strong reference OK - cleared in finish()
 
+        private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish
+
         private LayoutSpec mLastLayoutSpec;
 
         private WriteSpec mLastWriteSpec;
@@ -362,16 +396,42 @@
         private boolean mFinishRequested;
         private boolean mFinished;
 
-        public PrintDocumentAdapterDelegate(PrintDocumentAdapter documentAdapter, Looper looper) {
+        private boolean mDestroyed;
+
+        public PrintDocumentAdapterDelegate(Context context,
+                PrintDocumentAdapter documentAdapter) {
+            if (!(context instanceof Activity)) {
+                throw new IllegalStateException("Can print only from an activity");
+            }
+            mActivity = (Activity) context;
             mDocumentAdapter = documentAdapter;
-            mHandler = new MyHandler(looper);
+            mHandler = new MyHandler(mActivity.getMainLooper());
+            mActivity.getApplication().registerActivityLifecycleCallbacks(this);
+        }
+
+        @Override
+        public void setObserver(IPrintDocumentAdapterObserver observer) {
+            final boolean destroyed;
+            synchronized (mLock) {
+                if (!mDestroyed) {
+                    mObserver = observer;
+                }
+                destroyed = mDestroyed;
+            }
+            if (destroyed) {
+                try {
+                    observer.onDestroy();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
+                }
+            }
         }
 
         @Override
         public void start() {
             synchronized (mLock) {
-                // Started or finished - nothing to do.
-                if (mStartReqeusted || mFinishRequested) {
+                // Started called or finish called or destroyed - nothing to do.
+                if (mStartReqeusted || mFinishRequested || mDestroyed) {
                     return;
                 }
 
@@ -384,71 +444,85 @@
         @Override
         public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
                 ILayoutResultCallback callback, Bundle metadata, int sequence) {
+            final boolean destroyed;
             synchronized (mLock) {
-                // Start not called or finish called - nothing to do.
-                if (!mStartReqeusted || mFinishRequested) {
-                    return;
+                destroyed = mDestroyed;
+                // If start called and not finished called and not destroyed - do some work.
+                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
+                    // Layout cancels write and overrides layout.
+                    if (mLastWriteSpec != null) {
+                        IoUtils.closeQuietly(mLastWriteSpec.fd);
+                        mLastWriteSpec = null;
+                    }
+
+                    mLastLayoutSpec = new LayoutSpec();
+                    mLastLayoutSpec.callback = callback;
+                    mLastLayoutSpec.oldAttributes = oldAttributes;
+                    mLastLayoutSpec.newAttributes = newAttributes;
+                    mLastLayoutSpec.metadata = metadata;
+                    mLastLayoutSpec.sequence = sequence;
+
+                    // Cancel the previous cancellable operation.When the
+                    // cancellation completes we will do the pending work.
+                    if (cancelPreviousCancellableOperationLocked()) {
+                        return;
+                    }
+
+                    doPendingWorkLocked();
                 }
-
-                // Layout cancels write and overrides layout.
-                if (mLastWriteSpec != null) {
-                    IoUtils.closeQuietly(mLastWriteSpec.fd);
-                    mLastWriteSpec = null;
+            }
+            if (destroyed) {
+                try {
+                    callback.onLayoutFailed(null, sequence);
+                } catch (RemoteException re) {
+                    Log.i(LOG_TAG, "Error notifying for cancelled layout", re);
                 }
-
-                mLastLayoutSpec = new LayoutSpec();
-                mLastLayoutSpec.callback = callback;
-                mLastLayoutSpec.oldAttributes = oldAttributes;
-                mLastLayoutSpec.newAttributes = newAttributes;
-                mLastLayoutSpec.metadata = metadata;
-                mLastLayoutSpec.sequence = sequence;
-
-                // Cancel the previous cancellable operation.When the
-                // cancellation completes we will do the pending work.
-                if (cancelPreviousCancellableOperationLocked()) {
-                    return;
-                }
-
-                doPendingWorkLocked();
             }
         }
 
         @Override
         public void write(PageRange[] pages, ParcelFileDescriptor fd,
                 IWriteResultCallback callback, int sequence) {
+            final boolean destroyed;
             synchronized (mLock) {
-                // Start not called or finish called - nothing to do.
-                if (!mStartReqeusted || mFinishRequested) {
-                    return;
+                destroyed = mDestroyed;
+                // If start called and not finished called and not destroyed - do some work.
+                if (mStartReqeusted && !mFinishRequested && !mDestroyed) {
+                    // Write cancels previous writes.
+                    if (mLastWriteSpec != null) {
+                        IoUtils.closeQuietly(mLastWriteSpec.fd);
+                        mLastWriteSpec = null;
+                    }
+
+                    mLastWriteSpec = new WriteSpec();
+                    mLastWriteSpec.callback = callback;
+                    mLastWriteSpec.pages = pages;
+                    mLastWriteSpec.fd = fd;
+                    mLastWriteSpec.sequence = sequence;
+
+                    // Cancel the previous cancellable operation.When the
+                    // cancellation completes we will do the pending work.
+                    if (cancelPreviousCancellableOperationLocked()) {
+                        return;
+                    }
+
+                    doPendingWorkLocked();
                 }
-
-                // Write cancels previous writes.
-                if (mLastWriteSpec != null) {
-                    IoUtils.closeQuietly(mLastWriteSpec.fd);
-                    mLastWriteSpec = null;
+            }
+            if (destroyed) {
+                try {
+                    callback.onWriteFailed(null, sequence);
+                } catch (RemoteException re) {
+                    Log.i(LOG_TAG, "Error notifying for cancelled write", re);
                 }
-
-                mLastWriteSpec = new WriteSpec();
-                mLastWriteSpec.callback = callback;
-                mLastWriteSpec.pages = pages;
-                mLastWriteSpec.fd = fd;
-                mLastWriteSpec.sequence = sequence;
-
-                // Cancel the previous cancellable operation.When the
-                // cancellation completes we will do the pending work.
-                if (cancelPreviousCancellableOperationLocked()) {
-                    return;
-                }
-
-                doPendingWorkLocked();
             }
         }
 
         @Override
         public void finish() {
             synchronized (mLock) {
-                // Start not called or finish called - nothing to do.
-                if (!mStartReqeusted || mFinishRequested) {
+                // Start not called or finish called or destroyed - nothing to do.
+                if (!mStartReqeusted || mFinishRequested || mDestroyed) {
                     return;
                 }
 
@@ -467,15 +541,78 @@
             }
         }
 
+        @Override
+        public void onActivityPaused(Activity activity) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivityStarted(Activity activity) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivityResumed(Activity activity) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivityStopped(Activity activity) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+            /* do nothing */
+        }
+
+        @Override
+        public void onActivityDestroyed(Activity activity) {
+            // We really care only if the activity is being destroyed to
+            // notify the the print spooler so it can close the print dialog.
+            // Note the the spooler has a death recipient that observes if
+            // this process gets killed so we cover the case of onDestroy not
+            // being called due to this process being killed to reclaim memory.
+            final IPrintDocumentAdapterObserver observer;
+            synchronized (mLock) {
+                if (activity == mActivity) {
+                    mDestroyed = true;
+                    observer = mObserver;
+                    clearLocked();
+                } else {
+                    observer = null;
+                    activity = null;
+                }
+            }
+            if (observer != null) {
+                activity.getApplication().unregisterActivityLifecycleCallbacks(
+                        PrintDocumentAdapterDelegate.this);
+                try {
+                    observer.onDestroy();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error announcing destroyed state", re);
+                }
+            }
+        }
+
         private boolean isFinished() {
             return mDocumentAdapter == null;
         }
 
-        private void doFinish() {
+        private void clearLocked() {
+            mActivity = null;
             mDocumentAdapter = null;
             mHandler = null;
-            synchronized (mLock) {
-                mLayoutOrWriteCancellation = null;
+            mLayoutOrWriteCancellation = null;
+            mLastLayoutSpec = null;
+            if (mLastWriteSpec != null) {
+                IoUtils.closeQuietly(mLastWriteSpec.fd);
+                mLastWriteSpec = null;
             }
         }
 
@@ -536,25 +673,33 @@
                 }
                 switch (message.what) {
                     case MSG_START: {
-                        mDocumentAdapter.onStart();
+                        final PrintDocumentAdapter adapter;
+                        synchronized (mLock) {
+                            adapter = mDocumentAdapter;
+                        }
+                        if (adapter != null) {
+                            adapter.onStart();
+                        }
                     } break;
 
                     case MSG_LAYOUT: {
+                        final PrintDocumentAdapter adapter;
                         final CancellationSignal cancellation;
                         final LayoutSpec layoutSpec;
 
                         synchronized (mLock) {
+                            adapter = mDocumentAdapter;
                             layoutSpec = mLastLayoutSpec;
                             mLastLayoutSpec = null;
                             cancellation = new CancellationSignal();
                             mLayoutOrWriteCancellation = cancellation;
                         }
 
-                        if (layoutSpec != null) {
+                        if (layoutSpec != null && adapter != null) {
                             if (DEBUG) {
                                 Log.i(LOG_TAG, "Performing layout");
                             }
-                            mDocumentAdapter.onLayout(layoutSpec.oldAttributes,
+                            adapter.onLayout(layoutSpec.oldAttributes,
                                     layoutSpec.newAttributes, cancellation,
                                     new MyLayoutResultCallback(layoutSpec.callback,
                                             layoutSpec.sequence), layoutSpec.metadata);
@@ -562,21 +707,23 @@
                     } break;
 
                     case MSG_WRITE: {
+                        final PrintDocumentAdapter adapter;
                         final CancellationSignal cancellation;
                         final WriteSpec writeSpec;
 
                         synchronized (mLock) {
-                            writeSpec= mLastWriteSpec;
+                            adapter = mDocumentAdapter;
+                            writeSpec = mLastWriteSpec;
                             mLastWriteSpec = null;
                             cancellation = new CancellationSignal();
                             mLayoutOrWriteCancellation = cancellation;
                         }
 
-                        if (writeSpec != null) {
+                        if (writeSpec != null && adapter != null) {
                             if (DEBUG) {
                                 Log.i(LOG_TAG, "Performing write");
                             }
-                            mDocumentAdapter.onWrite(writeSpec.pages, writeSpec.fd,
+                            adapter.onWrite(writeSpec.pages, writeSpec.fd,
                                     cancellation, new MyWriteResultCallback(writeSpec.callback,
                                             writeSpec.fd, writeSpec.sequence));
                         }
@@ -586,8 +733,20 @@
                         if (DEBUG) {
                             Log.i(LOG_TAG, "Performing finish");
                         }
-                        mDocumentAdapter.onFinish();
-                        doFinish();
+                        final PrintDocumentAdapter adapter;
+                        final Activity activity;
+                        synchronized (mLock) {
+                            adapter = mDocumentAdapter;
+                            activity = mActivity;
+                            clearLocked();
+                        }
+                        if (adapter != null) {
+                            adapter.onFinish();
+                        }
+                        if (activity != null) {
+                            activity.getApplication().unregisterActivityLifecycleCallbacks(
+                                    PrintDocumentAdapterDelegate.this);
+                        }
                     } break;
 
                     default: {
@@ -727,17 +886,33 @@
     private static final class PrintJobStateChangeListenerWrapper extends
             IPrintJobStateChangeListener.Stub {
         private final WeakReference<PrintJobStateChangeListener> mWeakListener;
+        private final WeakReference<Handler> mWeakHandler;
 
-        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener) {
+        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
+                Handler handler) {
             mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
+            mWeakHandler = new WeakReference<Handler>(handler);
         }
 
         @Override
         public void onPrintJobStateChanged(PrintJobId printJobId) {
+            Handler handler = mWeakHandler.get();
             PrintJobStateChangeListener listener = mWeakListener.get();
-            if (listener != null) {
-                listener.onPrintJobsStateChanged(printJobId);
+            if (handler != null && listener != null) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = this;
+                args.arg2 = printJobId;
+                handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
+                        args).sendToTarget();
             }
         }
+
+        public void destroy() {
+            mWeakListener.clear();
+        }
+
+        public PrintJobStateChangeListener getListener() {
+            return mWeakListener.get();
+        }
     }
 }
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index d1dbedf..fdeb373 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -304,7 +304,7 @@
     /**
      * Gets the print job tag.
      *
-     * @return tag The tag or null.
+     * @return The tag or null.
      *
      * @see #setTag(String)
      */
@@ -313,6 +313,40 @@
         return getInfo().getTag();
     }
 
+    /**
+     * Gets the value of an advanced (printer specific) print option.
+     *
+     * @param key The option key.
+     * @return The option value.
+     */
+    public String getAdvancedStringOption(String key) {
+        PrintService.throwIfNotCalledOnMainThread();
+        return null;
+    }
+
+    /**
+     * Gets whether this job has a given advanced (printer specific) print
+     * option.
+     *
+     * @param key The option key.
+     * @return Whether the option is present.
+     */
+    public boolean hasAdvancedOption(String key) {
+        PrintService.throwIfNotCalledOnMainThread();
+        return false;
+    }
+
+    /**
+     * Gets the value of an advanced (printer specific) print option.
+     *
+     * @param key The option key.
+     * @return The option value.
+     */
+    public int getAdvancedIntOption(String key) {
+        PrintService.throwIfNotCalledOnMainThread();
+        return 0;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index e73a53b..0fc5f7f 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -16,6 +16,7 @@
 
 package android.printservice;
 
+import android.R;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
@@ -189,6 +190,28 @@
      */
     public static final String SERVICE_META_DATA = "android.printservice";
 
+    /**
+     * If you declared an optional activity with advanced print options via the
+     * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+     * attribute, this extra is used to pass in the currently constructed {@link
+     * PrintJobInfo} to your activity allowing you to modify it. After you are
+     * done, you must return the modified {@link PrintJobInfo} via the same extra.
+     * <p>
+     * You cannot modify the passed in {@link PrintJobInfo} directly, rather you
+     * should build another one using the {@link PrintJobInfo.Builder} class. You
+     * can specify any standard properties and add advanced, printer specific,
+     * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
+     * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link
+     * PrintJobInfo.Builder#putAdvancedOption(String, int)
+     * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options
+     * are not interpreted by the system, they will not be visible to applications,
+     * and can only be accessed by your print service via {@link
+     * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
+     * and {@link PrintJob#getAdvancedIntOption(String) PrintJob.getAdvancedIntOption(String)}.
+     * </p>
+     */
+    public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO";
+
     private Handler mHandler;
 
     private IPrintServiceClient mClient;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 1c14c38..7f8dca2 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -23,13 +23,14 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
 import android.graphics.Point;
+import android.media.ExifInterface;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -43,8 +44,10 @@
 import libcore.io.Libcore;
 
 import java.io.BufferedInputStream;
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.List;
 
@@ -69,16 +72,25 @@
     private DocumentsContract() {
     }
 
-    /** {@hide} */
-    @Deprecated
-    public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
-
     /**
-     * Intent action used to identify {@link DocumentsProvider} instances.
+     * Intent action used to identify {@link DocumentsProvider} instances. This
+     * is used in the {@code <intent-filter>} of a {@code <provider>}.
      */
     public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
 
     /** {@hide} */
+    public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+
+    /**
+     * Included in {@link AssetFileDescriptor#getExtras()} when returned
+     * thumbnail should be rotated.
+     *
+     * @see MediaStore.Images.ImageColumns#ORIENTATION
+     * @hide
+     */
+    public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION";
+
+    /** {@hide} */
     public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
     /** {@hide} */
     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
@@ -89,12 +101,14 @@
     private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES);
 
     /**
-     * Constants related to a document, including {@link Cursor} columns names
+     * Constants related to a document, including {@link Cursor} column names
      * and flags.
      * <p>
-     * A document can be either an openable file (with a specific MIME type), or
-     * a directory containing additional documents (with the
-     * {@link #MIME_TYPE_DIR} MIME type).
+     * A document can be either an openable stream (with a specific MIME type),
+     * or a directory containing additional documents (with the
+     * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a
+     * subtree containing zero or more documents, which can recursively contain
+     * even more documents and directories.
      * <p>
      * All columns are <em>read-only</em> to client applications.
      */
@@ -111,7 +125,7 @@
          * single document may be included as a child of multiple directories.
          * <p>
          * A provider must always return durable IDs, since they will be used to
-         * issue long-term Uri permission grants when an application interacts
+         * issue long-term URI permission grants when an application interacts
          * with {@link Intent#ACTION_OPEN_DOCUMENT} and
          * {@link Intent#ACTION_CREATE_DOCUMENT}.
          * <p>
@@ -279,8 +293,11 @@
     }
 
     /**
-     * Constants related to a root of documents, including {@link Cursor}
-     * columns names and flags.
+     * Constants related to a root of documents, including {@link Cursor} column
+     * names and flags. A root is the start of a tree of documents, such as a
+     * physical storage device, or an account. Each root starts at the directory
+     * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively
+     * contain both documents and directories.
      * <p>
      * All columns are <em>read-only</em> to client applications.
      */
@@ -318,7 +335,8 @@
 
         /**
          * Title for a root, which will be shown to a user. This column is
-         * required.
+         * required. For a single storage service surfacing multiple accounts as
+         * different roots, this title should be the name of the service.
          * <p>
          * Type: STRING
          */
@@ -326,7 +344,9 @@
 
         /**
          * Summary for this root, which may be shown to a user. This column is
-         * optional, and may be {@code null}.
+         * optional, and may be {@code null}. For a single storage service
+         * surfacing multiple accounts as different roots, this summary should
+         * be the name of the account.
          * <p>
          * Type: STRING
          */
@@ -382,11 +402,12 @@
         public static final int FLAG_LOCAL_ONLY = 1 << 1;
 
         /**
-         * Flag indicating that this root can report recently modified
-         * documents.
+         * Flag indicating that this root can be queried to provide recently
+         * modified documents.
          *
          * @see #COLUMN_FLAGS
          * @see DocumentsContract#buildRecentDocumentsUri(String, String)
+         * @see DocumentsProvider#queryRecentDocuments(String, String[])
          */
         public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
 
@@ -394,6 +415,8 @@
          * Flag indicating that this root supports search.
          *
          * @see #COLUMN_FLAGS
+         * @see DocumentsContract#buildSearchDocumentsUri(String, String,
+         *      String)
          * @see DocumentsProvider#querySearchDocuments(String, String,
          *      String[])
          */
@@ -470,7 +493,7 @@
     private static final String PARAM_MANAGE = "manage";
 
     /**
-     * Build Uri representing the roots of a document provider. When queried, a
+     * Build URI representing the roots of a document provider. When queried, a
      * provider will return one or more rows with columns defined by
      * {@link Root}.
      *
@@ -482,7 +505,7 @@
     }
 
     /**
-     * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a
+     * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a
      * document provider.
      *
      * @see #getRootId(Uri)
@@ -493,7 +516,7 @@
     }
 
     /**
-     * Build Uri representing the recently modified documents of a specific root
+     * Build URI representing the recently modified documents of a specific root
      * in a document provider. When queried, a provider will return zero or more
      * rows with columns defined by {@link Document}.
      *
@@ -507,7 +530,7 @@
     }
 
     /**
-     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+     * Build URI representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
      * document provider. When queried, a provider will return a single row with
      * columns defined by {@link Document}.
      *
@@ -520,7 +543,7 @@
     }
 
     /**
-     * Build Uri representing the children of the given directory in a document
+     * Build URI representing the children of the given directory in a document
      * provider. When queried, a provider will return zero or more rows with
      * columns defined by {@link Document}.
      *
@@ -537,7 +560,7 @@
     }
 
     /**
-     * Build Uri representing a search for matching documents under a specific
+     * Build URI representing a search for matching documents under a specific
      * root in a document provider. When queried, a provider will return zero or
      * more rows with columns defined by {@link Document}.
      *
@@ -553,7 +576,7 @@
     }
 
     /**
-     * Test if the given Uri represents a {@link Document} backed by a
+     * Test if the given URI represents a {@link Document} backed by a
      * {@link DocumentsProvider}.
      */
     public static boolean isDocumentUri(Context context, Uri uri) {
@@ -565,17 +588,19 @@
             return false;
         }
 
-        final ProviderInfo info = context.getPackageManager()
-                .resolveContentProvider(uri.getAuthority(), PackageManager.GET_META_DATA);
-        if (info != null && info.metaData != null && info.metaData.containsKey(
-                DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
-            return true;
+        final Intent intent = new Intent(PROVIDER_INTERFACE);
+        final List<ResolveInfo> infos = context.getPackageManager()
+                .queryIntentContentProviders(intent, 0);
+        for (ResolveInfo info : infos) {
+            if (uri.getAuthority().equals(info.providerInfo.authority)) {
+                return true;
+            }
         }
         return false;
     }
 
     /**
-     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
+     * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI.
      */
     public static String getRootId(Uri rootUri) {
         final List<String> paths = rootUri.getPathSegments();
@@ -589,7 +614,7 @@
     }
 
     /**
-     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
+     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
      */
     public static String getDocumentId(Uri documentUri) {
         final List<String> paths = documentUri.getPathSegments();
@@ -603,7 +628,7 @@
     }
 
     /**
-     * Extract the search query from a Uri built by
+     * Extract the search query from a URI built by
      * {@link #buildSearchDocumentsUri(String, String, String)}.
      */
     public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
@@ -621,7 +646,7 @@
     }
 
     /**
-     * Return thumbnail representing the document at the given Uri. Callers are
+     * Return thumbnail representing the document at the given URI. Callers are
      * responsible for their own in-memory caching.
      *
      * @param documentUri document to return thumbnail for, which must have
@@ -629,7 +654,7 @@
      * @param size optimal thumbnail size desired. A provider may return a
      *            thumbnail of a different size, but never more than double the
      *            requested size.
-     * @param signal signal used to indicate that caller is no longer interested
+     * @param signal signal used to indicate if caller is no longer interested
      *            in the thumbnail.
      * @return decoded thumbnail, or {@code null} if problem was encountered.
      * @see DocumentsProvider#openDocumentThumbnail(String, Point,
@@ -657,6 +682,7 @@
         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
 
         AssetFileDescriptor afd = null;
+        Bitmap bitmap = null;
         try {
             afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
 
@@ -688,21 +714,36 @@
 
             opts.inJustDecodeBounds = false;
             opts.inSampleSize = Math.min(widthSample, heightSample);
-            Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
             if (is != null) {
                 is.reset();
-                return BitmapFactory.decodeStream(is, null, opts);
+                bitmap = BitmapFactory.decodeStream(is, null, opts);
             } else {
                 try {
                     Libcore.os.lseek(fd, offset, SEEK_SET);
                 } catch (ErrnoException e) {
                     e.rethrowAsIOException();
                 }
-                return BitmapFactory.decodeFileDescriptor(fd, null, opts);
+                bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+            }
+
+            // Transform the bitmap if requested. We use a side-channel to
+            // communicate the orientation, since EXIF thumbnails don't contain
+            // the rotation flags of the original image.
+            final Bundle extras = afd.getExtras();
+            final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
+            if (orientation != 0) {
+                final int width = bitmap.getWidth();
+                final int height = bitmap.getHeight();
+
+                final Matrix m = new Matrix();
+                m.setRotate(orientation, width / 2, height / 2);
+                bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
             }
         } finally {
             IoUtils.closeQuietly(afd);
         }
+
+        return bitmap;
     }
 
     /**
@@ -770,4 +811,44 @@
 
         client.call(METHOD_DELETE_DOCUMENT, null, in);
     }
+
+    /**
+     * Open the given image for thumbnail purposes, using any embedded EXIF
+     * thumbnail if available, and providing orientation hints from the parent
+     * image.
+     *
+     * @hide
+     */
+    public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
+        final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+                file, ParcelFileDescriptor.MODE_READ_ONLY);
+        Bundle extras = null;
+
+        try {
+            final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
+
+            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
+                case ExifInterface.ORIENTATION_ROTATE_90:
+                    extras = new Bundle(1);
+                    extras.putInt(EXTRA_ORIENTATION, 90);
+                    break;
+                case ExifInterface.ORIENTATION_ROTATE_180:
+                    extras = new Bundle(1);
+                    extras.putInt(EXTRA_ORIENTATION, 180);
+                    break;
+                case ExifInterface.ORIENTATION_ROTATE_270:
+                    extras = new Bundle(1);
+                    extras.putInt(EXTRA_ORIENTATION, 270);
+                    break;
+            }
+
+            final long[] thumb = exif.getThumbnailRange();
+            if (thumb != null) {
+                return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
+            }
+        } catch (IOException e) {
+        }
+
+        return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
+    }
 }
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 337b735..c9efb53 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -24,6 +24,7 @@
 import static android.provider.DocumentsContract.getSearchDocumentsQuery;
 
 import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -39,6 +40,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.util.Log;
 
 import libcore.io.IoUtils;
@@ -46,25 +48,70 @@
 import java.io.FileNotFoundException;
 
 /**
- * Base class for a document provider. A document provider should extend this
- * class and implement the abstract methods.
+ * Base class for a document provider. A document provider offers read and write
+ * access to durable files, such as files stored on a local disk, or files in a
+ * cloud storage service. To create a document provider, extend this class,
+ * implement the abstract methods, and add it to your manifest like this:
+ *
+ * <pre class="prettyprint">&lt;manifest&gt;
+ *    ...
+ *    &lt;application&gt;
+ *        ...
+ *        &lt;provider
+ *            android:name="com.example.MyCloudProvider"
+ *            android:authorities="com.example.mycloudprovider"
+ *            android:exported="true"
+ *            android:grantUriPermissions="true"
+ *            android:permission="android.permission.MANAGE_DOCUMENTS"&gt;
+ *            &lt;intent-filter&gt;
+ *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
+ *            &lt;/intent-filter&gt;
+ *        &lt;/provider&gt;
+ *        ...
+ *    &lt;/application&gt;
+ *&lt;/manifest&gt;</pre>
  * <p>
- * Each document provider expresses one or more "roots" which each serve as the
- * top-level of a tree. For example, a root could represent an account, or a
- * physical storage device. Under each root, documents are referenced by
- * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
+ * When defining your provider, you must protect it with
+ * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
+ * only the system can obtain. Applications cannot use a documents provider
+ * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
+ * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
+ * navigate and select documents. When a user selects documents through that
+ * UI, the system issues narrow URI permission grants to the requesting
+ * application.
+ * </p>
+ * <h3>Documents</h3>
  * <p>
- * Documents can be either an openable file (with a specific MIME type), or a
+ * A document can be either an openable stream (with a specific MIME type), or a
  * directory containing additional documents (with the
- * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
- * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
- * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
+ * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
+ * of a subtree containing zero or more documents, which can recursively contain
+ * even more documents and directories.
+ * </p>
  * <p>
- * Document providers must be protected with the
- * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
- * only be requested by the system. The system-provided UI then issues narrow
- * Uri permission grants for individual documents when the user explicitly picks
- * documents.
+ * Each document can have different capabilities, as described by
+ * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
+ * as a thumbnail, a provider can set {@link Document#FLAG_SUPPORTS_THUMBNAIL}
+ * and implement
+ * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
+ * that thumbnail.
+ * </p>
+ * <p>
+ * Each document under a provider is uniquely referenced by its
+ * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
+ * single document can be included in multiple directories when responding to
+ * {@link #queryChildDocuments(String, String[], String)}. For example, a
+ * provider might surface a single photo in multiple locations: once in a
+ * directory of locations, and again in a directory of dates.
+ * </p>
+ * <h3>Roots</h3>
+ * <p>
+ * All documents are surfaced through one or more "roots." Each root represents
+ * the top of a document tree that a user can navigate. For example, a root
+ * could represent an account or a physical storage device. Similar to
+ * documents, each root can have capabilities expressed through
+ * {@link Root#COLUMN_FLAGS}.
+ * </p>
  *
  * @see Intent#ACTION_OPEN_DOCUMENT
  * @see Intent#ACTION_CREATE_DOCUMENT
@@ -114,25 +161,30 @@
     }
 
     /**
-     * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
-     * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
-     * represent the document, which must not change once returned.
+     * Create a new document and return its newly generated
+     * {@link Document#COLUMN_DOCUMENT_ID}. A provider must allocate a new
+     * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
+     * not change once returned.
      *
-     * @param documentId the parent directory to create the new document under.
-     * @param mimeType the MIME type associated with the new document.
-     * @param displayName the display name of the new document.
+     * @param parentDocumentId the parent directory to create the new document
+     *            under.
+     * @param mimeType the concrete MIME type associated with the new document.
+     *            If the MIME type is not supported, the provider must throw.
+     * @param displayName the display name of the new document. The provider may
+     *            alter this name to meet any internal constraints, such as
+     *            conflicting names.
      */
     @SuppressWarnings("unused")
-    public String createDocument(String documentId, String mimeType, String displayName)
+    public String createDocument(String parentDocumentId, String mimeType, String displayName)
             throws FileNotFoundException {
         throw new UnsupportedOperationException("Create not supported");
     }
 
     /**
-     * Delete the given document. Upon returning, any Uri permission grants for
-     * the given document will be revoked. If additional documents were deleted
-     * as a side effect of this call, such as documents inside a directory, the
-     * implementor is responsible for revoking those permissions.
+     * Delete the requested document. Upon returning, any URI permission grants
+     * for the requested document will be revoked. If additional documents were
+     * deleted as a side effect of this call, such as documents inside a
+     * directory, the implementor is responsible for revoking those permissions.
      *
      * @param documentId the document to delete.
      */
@@ -141,8 +193,35 @@
         throw new UnsupportedOperationException("Delete not supported");
     }
 
+    /**
+     * Return all roots currently provided. A provider must define at least one
+     * root to display to users, and it should avoid making network requests to
+     * keep this request fast.
+     * <p>
+     * Each root is defined by the metadata columns described in {@link Root},
+     * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
+     * representing a tree of documents to display under that root.
+     * <p>
+     * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
+     * android.database.ContentObserver)} to notify the system.
+     *
+     * @param projection list of {@link Root} columns to put into the cursor. If
+     *            {@code null} all supported columns should be included.
+     */
     public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
 
+    /**
+     * Return recently modified documents under the requested root. This will
+     * only be called for roots that advertise
+     * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
+     * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
+     * limited to only return the 64 most recently modified documents.
+     *
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @see DocumentsContract#EXTRA_LOADING
+     */
     @SuppressWarnings("unused")
     public Cursor queryRecentDocuments(String rootId, String[] projection)
             throws FileNotFoundException {
@@ -150,18 +229,43 @@
     }
 
     /**
-     * Return metadata for the given document. A provider should avoid making
-     * network requests to keep this request fast.
+     * Return metadata for the single requested document. A provider should
+     * avoid making network requests to keep this request fast.
      *
      * @param documentId the document to return.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
      */
     public abstract Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException;
 
     /**
-     * Return the children of the given document which is a directory.
+     * Return the children documents contained in the requested directory. This
+     * must only return immediate descendants, as additional queries will be
+     * issued to recursively explore the tree.
+     * <p>
+     * If your provider is cloud-based, and you have some data cached or pinned
+     * locally, you may return the local data immediately, setting
+     * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
+     * your provider is still fetching additional data. Then, when the network
+     * data is available, you can call {@link ContentResolver#notifyChange(Uri,
+     * android.database.ContentObserver)} to trigger a requery and return the
+     * complete contents.
      *
      * @param parentDocumentId the directory to return children for.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param sortOrder how to order the rows, formatted as an SQL
+     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
+     *            Passing {@code null} will use the default sort order, which
+     *            may be unordered. This ordering is a hint that can be used to
+     *            prioritize how data is fetched from the network, but UI may
+     *            always enforce a specific ordering.
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
      */
     public abstract Cursor queryChildDocuments(
             String parentDocumentId, String[] projection, String sortOrder)
@@ -176,9 +280,24 @@
     }
 
     /**
-     * Return documents that that match the given query.
+     * Return documents that that match the given query under the requested
+     * root. The returned documents should be sorted by relevance in descending
+     * order. How documents are matched against the query string is an
+     * implementation detail left to each provider, but it's suggested that at
+     * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
+     * case-insensitive fashion.
+     * <p>
+     * Only documents may be returned; directories are not supported in search
+     * results.
      *
      * @param rootId the root to search under.
+     * @param query string to match documents against.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
      */
     @SuppressWarnings("unused")
     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
@@ -187,8 +306,10 @@
     }
 
     /**
-     * Return MIME type for the given document. Must match the value of
-     * {@link Document#COLUMN_MIME_TYPE} for this document.
+     * Return concrete MIME type of the requested document. Must match the value
+     * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
+     * implementation queries {@link #queryDocument(String, String[])}, so
+     * providers may choose to override this as an optimization.
      */
     public String getDocumentType(String documentId) throws FileNotFoundException {
         final Cursor cursor = queryDocument(documentId, null);
@@ -204,18 +325,21 @@
     }
 
     /**
-     * Open and return the requested document. A provider should return a
-     * reliable {@link ParcelFileDescriptor} to detect when the remote caller
-     * has finished reading or writing the document. A provider may return a
-     * pipe or socket pair if the mode is exclusively
-     * {@link ParcelFileDescriptor#MODE_READ_ONLY} or
+     * Open and return the requested document.
+     * <p>
+     * A provider should return a reliable {@link ParcelFileDescriptor} to
+     * detect when the remote caller has finished reading or writing the
+     * document. A provider may return a pipe or socket pair if the mode is
+     * exclusively {@link ParcelFileDescriptor#MODE_READ_ONLY} or
      * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, but complex modes like
      * {@link ParcelFileDescriptor#MODE_READ_WRITE} require a normal file on
-     * disk. If a provider blocks while downloading content, it should
-     * periodically check {@link CancellationSignal#isCanceled()} to abort
-     * abandoned open requests.
+     * disk.
+     * <p>
+     * If a provider blocks while downloading content, it should periodically
+     * check {@link CancellationSignal#isCanceled()} to abort abandoned open
+     * requests.
      *
-     * @param docId the document to return.
+     * @param documentId the document to return.
      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
      * @param signal used by the caller to signal if the request should be
      *            cancelled.
@@ -223,20 +347,24 @@
      *      OnCloseListener)
      * @see ParcelFileDescriptor#createReliablePipe()
      * @see ParcelFileDescriptor#createReliableSocketPair()
+     * @see ParcelFileDescriptor#parseMode(String)
      */
     public abstract ParcelFileDescriptor openDocument(
-            String docId, String mode, CancellationSignal signal) throws FileNotFoundException;
+            String documentId, String mode, CancellationSignal signal) throws FileNotFoundException;
 
     /**
-     * Open and return a thumbnail of the requested document. A provider should
-     * return a thumbnail closely matching the hinted size, attempting to serve
-     * from a local cache if possible. A provider should never return images
-     * more than double the hinted size. If a provider performs expensive
-     * operations to download or generate a thumbnail, it should periodically
-     * check {@link CancellationSignal#isCanceled()} to abort abandoned
-     * thumbnail requests.
+     * Open and return a thumbnail of the requested document.
+     * <p>
+     * A provider should return a thumbnail closely matching the hinted size,
+     * attempting to serve from a local cache if possible. A provider should
+     * never return images more than double the hinted size.
+     * <p>
+     * If a provider performs expensive operations to download or generate a
+     * thumbnail, it should periodically check
+     * {@link CancellationSignal#isCanceled()} to abort abandoned thumbnail
+     * requests.
      *
-     * @param docId the document to return.
+     * @param documentId the document to return.
      * @param sizeHint hint of the optimal thumbnail dimensions.
      * @param signal used by the caller to signal if the request should be
      *            cancelled.
@@ -244,7 +372,8 @@
      */
     @SuppressWarnings("unused")
     public AssetFileDescriptor openDocumentThumbnail(
-            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+            String documentId, Point sizeHint, CancellationSignal signal)
+            throws FileNotFoundException {
         throw new UnsupportedOperationException("Thumbnails not supported");
     }
 
@@ -362,7 +491,7 @@
         final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
         final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
 
-        // Require that caller can manage given document
+        // Require that caller can manage requested document
         final boolean callerHasManage =
                 context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS)
                 == PackageManager.PERMISSION_GRANTED;
@@ -408,7 +537,7 @@
     }
 
     /**
-     * Implementation is provided by the parent class.
+     * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocument(String, String, CancellationSignal)
      */
@@ -418,7 +547,7 @@
     }
 
     /**
-     * Implementation is provided by the parent class.
+     * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocument(String, String, CancellationSignal)
      */
@@ -429,7 +558,7 @@
     }
 
     /**
-     * Implementation is provided by the parent class.
+     * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
      */
@@ -445,7 +574,7 @@
     }
 
     /**
-     * Implementation is provided by the parent class.
+     * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3d6721f..a960bba 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4470,7 +4470,7 @@
         public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
 
         /** @hide */
-        public static final String TRANSIENT_NAV_CONFIRMATIONS = "transient_nav_confirmations";
+        public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
 
         /**
          * This is the query URI for finding a print service to install.
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index dae47b8..6cda905 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -67,6 +67,14 @@
     public static final int DENSITY_XHIGH = 320;
 
     /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
+    public static final int DENSITY_400 = 400;
+
+    /**
      * Standard quantized DPI for extra-extra-high-density screens.  Applications
      * should not generally worry about this density; relying on XHIGH graphics
      * being scaled up to it should be sufficient for almost all cases.
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 8c1cf5f..abd173a 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -253,7 +253,7 @@
      * @param msg The message you would like logged.
      */
     public static int wtf(String tag, String msg) {
-        return wtf(tag, msg, null);
+        return wtf(LOG_ID_MAIN, tag, msg, null, false);
     }
 
     /**
@@ -262,7 +262,7 @@
      * @hide
      */
     public static int wtfStack(String tag, String msg) {
-        return wtfStack(LOG_ID_MAIN, tag, msg);
+        return wtf(LOG_ID_MAIN, tag, msg, null, true);
     }
 
     /**
@@ -272,7 +272,7 @@
      * @param tr An exception to log.
      */
     public static int wtf(String tag, Throwable tr) {
-        return wtf(tag, tr.getMessage(), tr);
+        return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false);
     }
 
     /**
@@ -283,18 +283,13 @@
      * @param tr An exception to log.  May be null.
      */
     public static int wtf(String tag, String msg, Throwable tr) {
-        return wtf(LOG_ID_MAIN, tag, msg, tr);
+        return wtf(LOG_ID_MAIN, tag, msg, tr, false);
     }
 
-    static int wtfStack(int logId, String tag, String msg) {
-        TerribleFailure here = new TerribleFailure("here", null);
-        here.fillInStackTrace();
-        return wtf(logId, tag, msg, here);
-    }
-
-    static int wtf(int logId, String tag, String msg, Throwable tr) {
+    static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack) {
         TerribleFailure what = new TerribleFailure(msg, tr);
-        int bytes = println_native(logId, ASSERT, tag, msg + '\n' + getStackTraceString(tr));
+        int bytes = println_native(logId, ASSERT, tag, msg + '\n'
+                + getStackTraceString(localStack ? what : tr));
         sWtfHandler.onTerribleFailure(tag, what);
         return bytes;
     }
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index a5c22ff..70795bb 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -79,19 +79,19 @@
     }
 
     public static int wtf(String tag, String msg) {
-        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null);
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, false);
     }
 
     public static int wtfStack(String tag, String msg) {
-        return Log.wtfStack(Log.LOG_ID_SYSTEM, tag, msg);
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, null, true);
     }
 
     public static int wtf(String tag, Throwable tr) {
-        return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr);
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, tr.getMessage(), tr, false);
     }
 
     public static int wtf(String tag, String msg, Throwable tr) {
-        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr);
+        return Log.wtf(Log.LOG_ID_SYSTEM, tag, msg, tr, false);
     }
 
     public static int println(int priority, String tag, String msg) {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index f28e4b5..f1523ae 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -23,6 +23,9 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
 
 /**
  * Coordinates the timing of animations, input and drawing.
@@ -256,6 +259,15 @@
         return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
     }
 
+    void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+        writer.print(prefix); writer.println("Choreographer:");
+        writer.print(innerPrefix); writer.print("mFrameScheduled=");
+                writer.println(mFrameScheduled);
+        writer.print(innerPrefix); writer.print("mLastFrameTime=");
+                writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
+    }
+
     /**
      * Posts a callback to run on the next frame.
      * <p>
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c774ebf..75dfdaf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -696,9 +697,23 @@
      */
     public static final int NO_ID = -1;
 
+    /**
+     * Signals that compatibility booleans have been initialized according to
+     * target SDK versions.
+     */
+    private static boolean sCompatibilityDone = false;
+
+    /**
+     * Use the old (broken) way of building MeasureSpecs.
+     */
     private static boolean sUseBrokenMakeMeasureSpec = false;
 
     /**
+     * Ignore any optimizations using the measure cache.
+     */
+    private static boolean sIgnoreMeasureCache = false;
+
+    /**
      * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
      * calling setFlags.
      */
@@ -3520,10 +3535,17 @@
         mUserPaddingStart = UNDEFINED_PADDING;
         mUserPaddingEnd = UNDEFINED_PADDING;
 
-        if (!sUseBrokenMakeMeasureSpec && context != null &&
-                context.getApplicationInfo().targetSdkVersion <= JELLY_BEAN_MR1) {
+        if (!sCompatibilityDone && context != null) {
+            final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
             // Older apps may need this compatibility hack for measurement.
-            sUseBrokenMakeMeasureSpec = true;
+            sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;
+
+            // Older apps expect onMeasure() to always be called on a layout pass, regardless
+            // of whether a layout was requested on that View.
+            sIgnoreMeasureCache = targetSdkVersion < KITKAT;
+
+            sCompatibilityDone = true;
         }
     }
 
@@ -9904,15 +9926,20 @@
     }
 
     /**
-     * Returns whether this View has content which overlaps. This function, intended to be
-     * overridden by specific View types, is an optimization when alpha is set on a view. If
-     * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer
-     * and then composited it into place, which can be expensive. If the view has no overlapping
-     * rendering, the view can draw each primitive with the appropriate alpha value directly.
-     * An example of overlapping rendering is a TextView with a background image, such as a
-     * Button. An example of non-overlapping rendering is a TextView with no background, or
-     * an ImageView with only the foreground image. The default implementation returns true;
-     * subclasses should override if they have cases which can be optimized.
+     * Returns whether this View has content which overlaps.
+     *
+     * <p>This function, intended to be overridden by specific View types, is an optimization when
+     * alpha is set on a view. If rendering overlaps in a view with alpha < 1, that view is drawn to
+     * an offscreen buffer and then composited into place, which can be expensive. If the view has
+     * no overlapping rendering, the view can draw each primitive with the appropriate alpha value
+     * directly. An example of overlapping rendering is a TextView with a background image, such as
+     * a Button. An example of non-overlapping rendering is a TextView with no background, or an
+     * ImageView with only the foreground image. The default implementation returns true; subclasses
+     * should override if they have cases which can be optimized.</p>
+     *
+     * <p>The current implementation of the saveLayer and saveLayerAlpha methods in {@link Canvas}
+     * necessitates that a View return true if it uses the methods internally without passing the
+     * {@link Canvas#CLIP_TO_LAYER_SAVE_FLAG}.</p>
      *
      * @return true if the content in this view might overlap, false otherwise.
      */
@@ -16567,7 +16594,7 @@
 
             int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                     mMeasureCache.indexOfKey(key);
-            if (cacheIndex < 0) {
+            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                 // measure ourselves, this should set the measured dimension flag back
                 onMeasure(widthMeasureSpec, heightMeasureSpec);
                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 273ee1e..105f25a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,8 +77,10 @@
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -1179,7 +1181,8 @@
             mFullRedrawNeeded = true;
             mLayoutRequested = true;
 
-            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) {
+            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
+                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                 // NOTE -- system code, won't try to do compat mode.
                 Point size = new Point();
                 mDisplay.getRealSize(size);
@@ -1273,7 +1276,8 @@
                         || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                     windowSizeMayChange = true;
 
-                    if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) {
+                    if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
+                            || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                         // NOTE -- system code, won't try to do compat mode.
                         Point size = new Point();
                         mDisplay.getRealSize(size);
@@ -1408,9 +1412,9 @@
 
                 final int surfaceGenerationId = mSurface.getGenerationId();
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
-                if (!mDrawDuringWindowsAnimating) {
-                    mWindowsAnimating |=
-                            (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0;
+                if (!mDrawDuringWindowsAnimating &&
+                        (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
+                    mWindowsAnimating = true;
                 }
 
                 if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
@@ -3461,6 +3465,12 @@
                 finishInputEvent(q);
             }
         }
+
+        void dump(String prefix, PrintWriter writer) {
+            if (mNext != null) {
+                mNext.dump(prefix, writer);
+            }
+        }
     }
 
     /**
@@ -3598,6 +3608,16 @@
             mQueueLength -= 1;
             Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
         }
+
+        @Override
+        void dump(String prefix, PrintWriter writer) {
+            writer.print(prefix);
+            writer.print(getClass().getName());
+            writer.print(": mQueueLength=");
+            writer.println(mQueueLength);
+
+            super.dump(prefix, writer);
+        }
     }
 
     /**
@@ -3803,6 +3823,9 @@
             if (q.mEvent instanceof KeyEvent) {
                 return processKeyEvent(q);
             } else {
+                // If delivering a new non-key event, make sure the window is
+                // now allowed to start updating.
+                handleDispatchDoneAnimating();
                 final int source = q.mEvent.getSource();
                 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                     return processPointerEvent(q);
@@ -3817,6 +3840,12 @@
         private int processKeyEvent(QueuedInputEvent q) {
             final KeyEvent event = (KeyEvent)q.mEvent;
 
+            if (event.getAction() != KeyEvent.ACTION_UP) {
+                // If delivering a new key event, make sure the window is
+                // now allowed to start updating.
+                handleDispatchDoneAnimating();
+            }
+
             // Deliver the key to the view hierarchy.
             if (mView.dispatchKeyEvent(event)) {
                 return FINISH_HANDLED;
@@ -5195,6 +5224,53 @@
         mView.debug();
     }
 
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        String innerPrefix = prefix + "  ";
+        writer.print(prefix); writer.println("ViewRoot:");
+        writer.print(innerPrefix); writer.print("mAdded="); writer.print(mAdded);
+                writer.print(" mRemoved="); writer.println(mRemoved);
+        writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
+                writer.println(mConsumeBatchedInputScheduled);
+        writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
+                writer.println(mPendingInputEventCount);
+        writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
+                writer.println(mProcessInputEventsScheduled);
+        writer.print(innerPrefix); writer.print("mTraversalScheduled=");
+                writer.print(mTraversalScheduled);
+        if (mTraversalScheduled) {
+            writer.print(" (barrier="); writer.print(mTraversalBarrier); writer.println(")");
+        } else {
+            writer.println();
+        }
+        mFirstInputStage.dump(innerPrefix, writer);
+
+        mChoreographer.dump(prefix, writer);
+
+        writer.print(prefix); writer.println("View Hierarchy:");
+        dumpViewHierarchy(innerPrefix, writer, mView);
+    }
+
+    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
+        writer.print(prefix);
+        if (view == null) {
+            writer.println("null");
+            return;
+        }
+        writer.println(view.toString());
+        if (!(view instanceof ViewGroup)) {
+            return;
+        }
+        ViewGroup grp = (ViewGroup)view;
+        final int N = grp.getChildCount();
+        if (N <= 0) {
+            return;
+        }
+        prefix = prefix + "  ";
+        for (int i=0; i<N; i++) {
+            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
+        }
+    }
+
     public void dumpGfxInfo(int[] info) {
         info[0] = info[1] = 0;
         if (mView != null) {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index ec09bc9..d9e140e 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1163,6 +1163,11 @@
     public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
 
     /**
+     * @return The current height of the input method window.
+     */
+    public int getInputMethodWindowVisibleHeightLw();
+
+    /**
      * Called when the current user changes. Guaranteed to be called before the broadcast
      * of the new user id is made to all listeners.
      *
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f635eee..8b91155 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -722,7 +722,7 @@
     int mAction;
     int mContentChangeTypes;
 
-    private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
+    private ArrayList<AccessibilityRecord> mRecords;
 
     /*
      * Hide constructor from clients.
@@ -755,11 +755,13 @@
     @Override
     public void setSealed(boolean sealed) {
         super.setSealed(sealed);
-        List<AccessibilityRecord> records = mRecords;
-        final int recordCount = records.size();
-        for (int i = 0; i < recordCount; i++) {
-            AccessibilityRecord record = records.get(i);
-            record.setSealed(sealed);
+        final List<AccessibilityRecord> records = mRecords;
+        if (records != null) {
+            final int recordCount = records.size();
+            for (int i = 0; i < recordCount; i++) {
+                AccessibilityRecord record = records.get(i);
+                record.setSealed(sealed);
+            }
         }
     }
 
@@ -769,7 +771,7 @@
      * @return The number of records.
      */
     public int getRecordCount() {
-        return mRecords.size();
+        return mRecords == null ? 0 : mRecords.size();
     }
 
     /**
@@ -781,6 +783,9 @@
      */
     public void appendRecord(AccessibilityRecord record) {
         enforceNotSealed();
+        if (mRecords == null) {
+            mRecords = new ArrayList<AccessibilityRecord>();
+        }
         mRecords.add(record);
     }
 
@@ -791,6 +796,9 @@
      * @return The record at the specified index.
      */
     public AccessibilityRecord getRecord(int index) {
+        if (mRecords == null) {
+            throw new IndexOutOfBoundsException("Invalid index " + index + ", size is 0");
+        }
         return mRecords.get(index);
     }
 
@@ -964,11 +972,14 @@
         AccessibilityEvent eventClone = AccessibilityEvent.obtain();
         eventClone.init(event);
 
-        final int recordCount = event.mRecords.size();
-        for (int i = 0; i < recordCount; i++) {
-            AccessibilityRecord record = event.mRecords.get(i);
-            AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
-            eventClone.mRecords.add(recordClone);
+        if (event.mRecords != null) {
+            final int recordCount = event.mRecords.size();
+            eventClone.mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+            for (int i = 0; i < recordCount; i++) {
+                final AccessibilityRecord record = event.mRecords.get(i);
+                final AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
+                eventClone.mRecords.add(recordClone);
+            }
         }
 
         return eventClone;
@@ -1013,9 +1024,11 @@
         mContentChangeTypes = 0;
         mPackageName = null;
         mEventTime = 0;
-        while (!mRecords.isEmpty()) {
-            AccessibilityRecord record = mRecords.remove(0);
-            record.recycle();
+        if (mRecords != null) {
+            while (!mRecords.isEmpty()) {
+                AccessibilityRecord record = mRecords.remove(0);
+                record.recycle();
+            }
         }
     }
 
@@ -1037,11 +1050,14 @@
 
         // Read the records.
         final int recordCount = parcel.readInt();
-        for (int i = 0; i < recordCount; i++) {
-            AccessibilityRecord record = AccessibilityRecord.obtain();
-            readAccessibilityRecordFromParcel(record, parcel);
-            record.mConnectionId = mConnectionId;
-            mRecords.add(record);
+        if (recordCount > 0) {
+            mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+            for (int i = 0; i < recordCount; i++) {
+                AccessibilityRecord record = AccessibilityRecord.obtain();
+                readAccessibilityRecordFromParcel(record, parcel);
+                record.mConnectionId = mConnectionId;
+                mRecords.add(record);
+            }
         }
     }
 
@@ -1147,8 +1163,8 @@
             builder.append("; ContentChangeTypes: ").append(mContentChangeTypes);
             builder.append("; sourceWindowId: ").append(mSourceWindowId);
             builder.append("; mSourceNodeId: ").append(mSourceNodeId);
-            for (int i = 0; i < mRecords.size(); i++) {
-                AccessibilityRecord record = mRecords.get(i);
+            for (int i = 0; i < getRecordCount(); i++) {
+                final AccessibilityRecord record = getRecord(i);
                 builder.append("  Record ");
                 builder.append(i);
                 builder.append(":");
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 020b92c..324ba77 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -127,25 +127,45 @@
 
     boolean mIsTouchExplorationEnabled;
 
-    final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners =
-        new CopyOnWriteArrayList<AccessibilityStateChangeListener>();
+    private final CopyOnWriteArrayList<AccessibilityStateChangeListener>
+            mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<
+                    AccessibilityStateChangeListener>();
+
+    private final CopyOnWriteArrayList<TouchExplorationStateChangeListener>
+            mTouchExplorationStateChangeListeners = new CopyOnWriteArrayList<
+                    TouchExplorationStateChangeListener>();
 
     /**
-     * Listener for the system accessibility state. To listen for changes to the accessibility
-     * state on the device, implement this interface and register it with the system by
-     * calling {@link AccessibilityManager#addAccessibilityStateChangeListener
-     * addAccessibilityStateChangeListener()}.
+     * Listener for the system accessibility state. To listen for changes to the
+     * accessibility state on the device, implement this interface and register
+     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called back on change in the accessibility state.
+         * Called when the accessibility enabled state changes.
          *
          * @param enabled Whether accessibility is enabled.
          */
         public void onAccessibilityStateChanged(boolean enabled);
     }
 
+    /**
+     * Listener for the system touch exploration state. To listen for changes to
+     * the touch exploration state on the device, implement this interface and
+     * register it with the system by calling
+     * {@link #addTouchExplorationStateChangeListener}.
+     */
+    public interface TouchExplorationStateChangeListener {
+
+        /**
+         * Called when the touch exploration enabled state changes.
+         *
+         * @param enabled Whether touch exploration is enabled.
+         */
+        public void onTouchExplorationStateChanged(boolean enabled);
+    }
+
     final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
         public void setState(int state) {
             mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
@@ -399,34 +419,61 @@
     }
 
     /**
-     * Sets the current state.
+     * Registers a {@link TouchExplorationStateChangeListener} for changes in
+     * the global touch exploration state of the system.
+     *
+     * @param listener The listener.
+     * @return True if successfully registered.
+     */
+    public boolean addTouchExplorationStateChangeListener(
+            TouchExplorationStateChangeListener listener) {
+        return mTouchExplorationStateChangeListeners.add(listener);
+    }
+
+    /**
+     * Unregisters a {@link TouchExplorationStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if successfully unregistered.
+     */
+    public boolean removeTouchExplorationStateChangeListener(
+            TouchExplorationStateChangeListener listener) {
+        return mTouchExplorationStateChangeListeners.remove(listener);
+    }
+
+    /**
+     * Sets the current state and notifies listeners, if necessary.
      *
      * @param stateFlags The state flags.
      */
     private void setState(int stateFlags) {
-        final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
-        setAccessibilityState(accessibilityEnabled);
-        mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
-    }
-
-    /**
-     * Sets the enabled state.
-     *
-     * @param isEnabled The accessibility state.
-     */
-    private void setAccessibilityState(boolean isEnabled) {
+        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+        final boolean touchExplorationEnabled =
+                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
         synchronized (mHandler) {
-            if (isEnabled != mIsEnabled) {
-                mIsEnabled = isEnabled;
-                notifyAccessibilityStateChanged();
+            final boolean wasEnabled = mIsEnabled;
+            final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+
+            // Ensure listeners get current state from isZzzEnabled() calls.
+            mIsEnabled = enabled;
+            mIsTouchExplorationEnabled = touchExplorationEnabled;
+
+            if (wasEnabled != enabled) {
+                notifyAccessibilityStateChangedLh();
+            }
+
+            if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+                notifyTouchExplorationStateChangedLh();
             }
         }
     }
 
     /**
      * Notifies the registered {@link AccessibilityStateChangeListener}s.
+     * <p>
+     * The caller must be locked on {@link #mHandler}.
      */
-    private void notifyAccessibilityStateChanged() {
+    private void notifyAccessibilityStateChangedLh() {
         final int listenerCount = mAccessibilityStateChangeListeners.size();
         for (int i = 0; i < listenerCount; i++) {
             mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
@@ -434,6 +481,19 @@
     }
 
     /**
+     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+     * <p>
+     * The caller must be locked on {@link #mHandler}.
+     */
+    private void notifyTouchExplorationStateChangedLh() {
+        final int listenerCount = mTouchExplorationStateChangeListeners.size();
+        for (int i = 0; i < listenerCount; i++) {
+            mTouchExplorationStateChangeListeners.get(i)
+                    .onTouchExplorationStateChanged(mIsTouchExplorationEnabled);
+        }
+    }
+
+    /**
      * Adds an accessibility interaction connection interface for a given window.
      * @param windowToken The window token to which a connection is added.
      * @param connection The connection.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9fc37cf..4f53c1e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2540,7 +2540,7 @@
 
     /**
      * Class with information if a node is a range. Use
-     * {@link RangeInfo#obtain(int, float, float, float) to get an instance.
+     * {@link RangeInfo#obtain(int, float, float, float)} to get an instance.
      */
     public static final class RangeInfo {
         private static final int MAX_POOL_SIZE = 10;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index 7dd1e8a..a9473a8 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -67,11 +67,12 @@
         if (ENABLED) {
             final int eventType = event.getEventType();
             switch (eventType) {
+                case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
                 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+                    // If the active window changes, clear the cache.
                     final int windowId = event.getWindowId();
-                    // If a new window, we clear the cache.
                     if (mWindowId != windowId) {
                         mWindowId = windowId;
                         clear();
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index c440c7b..5df5811 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -45,6 +45,17 @@
 
 /**
  * This class is used to specify meta information of an input method.
+ *
+ * <p>It should be defined in an XML resource file with an {@code &lt;input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodSubtype
+ *
+ * @attr ref android.R.styleable#InputMethod_settingsActivity
+ * @attr ref android.R.styleable#InputMethod_isDefault
+ * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
  */
 public final class InputMethodInfo implements Parcelable {
     static final String TAG = "InputMethodInfo";
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53f7c79..cd89de7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1840,6 +1840,21 @@
     }
 
     /**
+     * @return The current height of the input method window.
+     * @hide
+     */
+    public int getInputMethodWindowVisibleHeight() {
+        synchronized (mH) {
+            try {
+                return mService.getInputMethodWindowVisibleHeight();
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+                return 0;
+            }
+        }
+    }
+
+    /**
      * Force switch to the last used input method and subtype. If the last input method didn't have
      * any subtypes, the framework will simply switch to the last input method with no subtype
      * specified.
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index eee09d2..84072bf06 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -38,9 +38,22 @@
  * the specified subtype of the designated IME directly.
  *
  * <p>It should be defined in an XML resource file of the input method with the
- * <code>&lt;subtype&gt;</code> element. For more information, see the guide to
- * <a href="{@docRoot}resources/articles/creating-input-method.html">
+ * <code>&lt;subtype&gt;</code> element, which resides within an {@code &lt;input-method>} element.
+ * For more information, see the guide to
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
  * Creating an Input Method</a>.</p>
+ *
+ * @see InputMethodInfo
+ *
+ * @attr ref android.R.styleable#InputMethod_Subtype_label
+ * @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
+ * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
+ * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
+ * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
+ * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
  */
 public final class InputMethodSubtype implements Parcelable {
     private static final String TAG = InputMethodSubtype.class.getSimpleName();
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
deleted file mode 100644
index abc078b..0000000
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ /dev/null
@@ -1,976 +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;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.speech.tts.TextToSpeech;
-import android.speech.tts.TextToSpeech.Engine;
-import android.speech.tts.TextToSpeech.OnInitListener;
-import android.speech.tts.UtteranceProgressListener;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.webkit.WebViewCore.EventHub;
-
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Handles injecting accessibility JavaScript and related JavaScript -> Java
- * APIs.
- */
-class AccessibilityInjector {
-    private static final String TAG = AccessibilityInjector.class.getSimpleName();
-
-    private static boolean DEBUG = false;
-
-    // The WebViewClassic this injector is responsible for managing.
-    private final WebViewClassic mWebViewClassic;
-
-    // Cached reference to mWebViewClassic.getContext(), for convenience.
-    private final Context mContext;
-
-    // Cached reference to mWebViewClassic.getWebView(), for convenience.
-    private final WebView mWebView;
-
-    // The Java objects that are exposed to JavaScript.
-    private TextToSpeechWrapper mTextToSpeech;
-    private CallbackHandler mCallback;
-
-    // Lazily loaded helper objects.
-    private AccessibilityManager mAccessibilityManager;
-    private AccessibilityInjectorFallback mAccessibilityInjectorFallback;
-    private JSONObject mAccessibilityJSONObject;
-
-    // Whether the accessibility script has been injected into the current page.
-    private boolean mAccessibilityScriptInjected;
-
-    // Constants for determining script injection strategy.
-    private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
-    private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
-    @SuppressWarnings("unused")
-    private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
-
-    // Alias for TTS API exposed to JavaScript.
-    private static final String ALIAS_TTS_JS_INTERFACE = "accessibility";
-
-    // Alias for traversal callback exposed to JavaScript.
-    private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal";
-
-    // Template for JavaScript that injects a screen-reader.
-    private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
-            "javascript:(function() {" +
-                    "    var chooser = document.createElement('script');" +
-                    "    chooser.type = 'text/javascript';" +
-                    "    chooser.src = '%1s';" +
-                    "    document.getElementsByTagName('head')[0].appendChild(chooser);" +
-                    "  })();";
-
-    // Template for JavaScript that performs AndroidVox actions.
-    private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE =
-            "(function() {" +
-                    "  if ((typeof(cvox) != 'undefined')" +
-                    "      && (cvox != null)" +
-                    "      && (typeof(cvox.ChromeVox) != 'undefined')" +
-                    "      && (cvox.ChromeVox != null)" +
-                    "      && (typeof(cvox.AndroidVox) != 'undefined')" +
-                    "      && (cvox.AndroidVox != null)" +
-                    "      && cvox.ChromeVox.isActive) {" +
-                    "    return cvox.AndroidVox.performAction('%1s');" +
-                    "  } else {" +
-                    "    return false;" +
-                    "  }" +
-                    "})()";
-
-    // JS code used to shut down an active AndroidVox instance.
-    private static final String TOGGLE_CVOX_TEMPLATE =
-            "javascript:(function() {" +
-                    "  if ((typeof(cvox) != 'undefined')" +
-                    "      && (cvox != null)" +
-                    "      && (typeof(cvox.ChromeVox) != 'undefined')" +
-                    "      && (cvox.ChromeVox != null)" +
-                    "      && (typeof(cvox.ChromeVox.host) != 'undefined')" +
-                    "      && (cvox.ChromeVox.host != null)) {" +
-                    "    cvox.ChromeVox.host.activateOrDeactivateChromeVox(%b);" +
-                    "  }" +
-                    "})();";
-
-    /**
-     * Creates an instance of the AccessibilityInjector based on
-     * {@code webViewClassic}.
-     *
-     * @param webViewClassic The WebViewClassic that this AccessibilityInjector
-     *            manages.
-     */
-    public AccessibilityInjector(WebViewClassic webViewClassic) {
-        mWebViewClassic = webViewClassic;
-        mWebView = webViewClassic.getWebView();
-        mContext = webViewClassic.getContext();
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-    }
-
-    /**
-     * If JavaScript is enabled, pauses or resumes AndroidVox.
-     *
-     * @param enabled Whether feedback should be enabled.
-     */
-    public void toggleAccessibilityFeedback(boolean enabled) {
-        if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
-            return;
-        }
-
-        toggleAndroidVox(enabled);
-
-        if (!enabled && (mTextToSpeech != null)) {
-            mTextToSpeech.stop();
-        }
-    }
-
-    /**
-     * Attempts to load scripting interfaces for accessibility.
-     * <p>
-     * This should only be called before a page loads.
-     */
-    public void addAccessibilityApisIfNecessary() {
-        if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
-            return;
-        }
-
-        addTtsApis();
-        addCallbackApis();
-    }
-
-    /**
-     * Attempts to unload scripting interfaces for accessibility.
-     * <p>
-     * This should only be called before a page loads.
-     */
-    private void removeAccessibilityApisIfNecessary() {
-        removeTtsApis();
-        removeCallbackApis();
-    }
-
-    /**
-     * Destroys this accessibility injector.
-     */
-    public void destroy() {
-        if (mTextToSpeech != null) {
-            mTextToSpeech.shutdown();
-            mTextToSpeech = null;
-        }
-
-        if (mCallback != null) {
-            mCallback = null;
-        }
-    }
-
-    private void toggleAndroidVox(boolean state) {
-        if (!mAccessibilityScriptInjected) {
-            return;
-        }
-
-        final String code = String.format(TOGGLE_CVOX_TEMPLATE, state);
-        mWebView.loadUrl(code);
-    }
-
-    /**
-     * Initializes an {@link AccessibilityNodeInfo} with the actions and
-     * movement granularity levels supported by this
-     * {@link AccessibilityInjector}.
-     * <p>
-     * If an action identifier is added in this method, this
-     * {@link AccessibilityInjector} should also return {@code true} from
-     * {@link #supportsAccessibilityAction(int)}.
-     * </p>
-     *
-     * @param info The info to initialize.
-     * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
-     */
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
-                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
-                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
-                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
-                | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
-        info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
-        info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
-        info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-        info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-        info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
-        info.setClickable(true);
-    }
-
-    /**
-     * Returns {@code true} if this {@link AccessibilityInjector} should handle
-     * the specified action.
-     *
-     * @param action An accessibility action identifier.
-     * @return {@code true} if this {@link AccessibilityInjector} should handle
-     *         the specified action.
-     */
-    public boolean supportsAccessibilityAction(int action) {
-        switch (action) {
-            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-            case AccessibilityNodeInfo.ACTION_CLICK:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * Performs the specified accessibility action.
-     *
-     * @param action The identifier of the action to perform.
-     * @param arguments The action arguments, or {@code null} if no arguments.
-     * @return {@code true} if the action was successful.
-     * @see View#performAccessibilityAction(int, Bundle)
-     */
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (!isAccessibilityEnabled()) {
-            mAccessibilityScriptInjected = false;
-            toggleFallbackAccessibilityInjector(false);
-            return false;
-        }
-
-        if (mAccessibilityScriptInjected) {
-            return sendActionToAndroidVox(action, arguments);
-        }
-
-        if (mAccessibilityInjectorFallback != null) {
-            return mAccessibilityInjectorFallback.performAccessibilityAction(action, arguments);
-        }
-
-        return false;
-    }
-
-    /**
-     * Attempts to handle key events when accessibility is turned on.
-     *
-     * @param event The key event to handle.
-     * @return {@code true} if the event was handled.
-     */
-    public boolean handleKeyEventIfNecessary(KeyEvent event) {
-        if (!isAccessibilityEnabled()) {
-            mAccessibilityScriptInjected = false;
-            toggleFallbackAccessibilityInjector(false);
-            return false;
-        }
-
-        if (mAccessibilityScriptInjected) {
-            // if an accessibility script is injected we delegate to it the key
-            // handling. this script is a screen reader which is a fully fledged
-            // solution for blind users to navigate in and interact with web
-            // pages.
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-                mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
-            } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
-            } else {
-                return false;
-            }
-
-            return true;
-        }
-
-        if (mAccessibilityInjectorFallback != null) {
-            // if an accessibility injector is present (no JavaScript enabled or
-            // the site opts out injecting our JavaScript screen reader) we let
-            // it decide whether to act on and consume the event.
-            return mAccessibilityInjectorFallback.onKeyEvent(event);
-        }
-
-        return false;
-    }
-
-    /**
-     * Attempts to handle selection change events when accessibility is using a
-     * non-JavaScript method.
-     * <p>
-     * This must not be called from the main thread.
-     *
-     * @param selection The selection string.
-     * @param token The selection request token.
-     */
-    public void onSelectionStringChangedWebCoreThread(String selection, int token) {
-        if (mAccessibilityInjectorFallback != null) {
-            mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
-        }
-    }
-
-    /**
-     * Prepares for injecting accessibility scripts into a new page.
-     *
-     * @param url The URL that will be loaded.
-     */
-    public void onPageStarted(String url) {
-        mAccessibilityScriptInjected = false;
-        if (DEBUG) {
-            Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page");
-        }
-        addAccessibilityApisIfNecessary();
-    }
-
-    /**
-     * Attempts to inject the accessibility script using a {@code <script>} tag.
-     * <p>
-     * This should be called after a page has finished loading.
-     * </p>
-     *
-     * @param url The URL that just finished loading.
-     */
-    public void onPageFinished(String url) {
-        if (!isAccessibilityEnabled()) {
-            toggleFallbackAccessibilityInjector(false);
-            return;
-        }
-
-        toggleFallbackAccessibilityInjector(true);
-
-        if (shouldInjectJavaScript(url)) {
-            // If we're supposed to use the JS screen reader, request a
-            // callback to confirm that CallbackHandler is working.
-            if (DEBUG) {
-                Log.d(TAG, "[" + mWebView.hashCode() + "] Request callback ");
-            }
-
-            mCallback.requestCallback(mWebView, mInjectScriptRunnable);
-        }
-    }
-
-    /**
-     * Runnable used to inject the JavaScript-based screen reader if the
-     * {@link CallbackHandler} API was successfully exposed to JavaScript.
-     */
-    private Runnable mInjectScriptRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) {
-                Log.d(TAG, "[" + mWebView.hashCode() + "] Received callback");
-            }
-
-            injectJavaScript();
-        }
-    };
-
-    /**
-     * Called by {@link #mInjectScriptRunnable} to inject the JavaScript-based
-     * screen reader after confirming that the {@link CallbackHandler} API is
-     * functional.
-     */
-    private void injectJavaScript() {
-        toggleFallbackAccessibilityInjector(false);
-
-        if (!mAccessibilityScriptInjected) {
-            mAccessibilityScriptInjected = true;
-            final String injectionUrl = getScreenReaderInjectionUrl();
-            mWebView.loadUrl(injectionUrl);
-            if (DEBUG) {
-                Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView");
-            }
-        } else {
-            if (DEBUG) {
-                Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice");
-            }
-        }
-    }
-
-    /**
-     * Adjusts the accessibility injection state to reflect changes in the
-     * JavaScript enabled state.
-     *
-     * @param enabled Whether JavaScript is enabled.
-     */
-    public void updateJavaScriptEnabled(boolean enabled) {
-        if (enabled) {
-            addAccessibilityApisIfNecessary();
-        } else {
-            removeAccessibilityApisIfNecessary();
-        }
-
-        // We have to reload the page after adding or removing APIs.
-        mWebView.reload();
-    }
-
-    /**
-     * Toggles the non-JavaScript method for handling accessibility.
-     *
-     * @param enabled {@code true} to enable the non-JavaScript method, or
-     *            {@code false} to disable it.
-     */
-    private void toggleFallbackAccessibilityInjector(boolean enabled) {
-        if (enabled && (mAccessibilityInjectorFallback == null)) {
-            mAccessibilityInjectorFallback = new AccessibilityInjectorFallback(mWebViewClassic);
-        } else {
-            mAccessibilityInjectorFallback = null;
-        }
-    }
-
-    /**
-     * Determines whether it's okay to inject JavaScript into a given URL.
-     *
-     * @param url The URL to check.
-     * @return {@code true} if JavaScript should be injected, {@code false} if a
-     *         non-JavaScript method should be used.
-     */
-    private boolean shouldInjectJavaScript(String url) {
-        // Respect the WebView's JavaScript setting.
-        if (!isJavaScriptEnabled()) {
-            return false;
-        }
-
-        // Allow the page to opt out of Accessibility script injection.
-        if (getAxsUrlParameterValue(url) == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
-            return false;
-        }
-
-        // The user must explicitly enable Accessibility script injection.
-        if (!isScriptInjectionEnabled()) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * @return {@code true} if the user has explicitly enabled Accessibility
-     *         script injection.
-     */
-    private boolean isScriptInjectionEnabled() {
-        final int injectionSetting = Settings.Secure.getInt(
-                mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0);
-        return (injectionSetting == 1);
-    }
-
-    /**
-     * Attempts to initialize and add interfaces for TTS, if that hasn't already
-     * been done.
-     */
-    private void addTtsApis() {
-        if (mTextToSpeech == null) {
-            mTextToSpeech = new TextToSpeechWrapper(mContext);
-        }
-
-        mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE);
-    }
-
-    /**
-     * Attempts to shutdown and remove interfaces for TTS, if that hasn't
-     * already been done.
-     */
-    private void removeTtsApis() {
-        if (mTextToSpeech != null) {
-            mTextToSpeech.stop();
-            mTextToSpeech.shutdown();
-            mTextToSpeech = null;
-        }
-
-        mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE);
-    }
-
-    private void addCallbackApis() {
-        if (mCallback == null) {
-            mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE);
-        }
-
-        mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE);
-    }
-
-    private void removeCallbackApis() {
-        if (mCallback != null) {
-            mCallback = null;
-        }
-
-        mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE);
-    }
-
-    /**
-     * Returns the script injection preference requested by the URL, or
-     * {@link #ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED} if the page has no
-     * preference.
-     *
-     * @param url The URL to check.
-     * @return A script injection preference.
-     */
-    private int getAxsUrlParameterValue(String url) {
-        if (url == null) {
-            return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
-        }
-
-        try {
-            final List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), null);
-
-            for (NameValuePair param : params) {
-                if ("axs".equals(param.getName())) {
-                    return verifyInjectionValue(param.getValue());
-                }
-            }
-        } catch (URISyntaxException e) {
-            // Do nothing.
-        } catch (IllegalArgumentException e) {
-            // Catch badly-formed URLs.
-        }
-
-        return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
-    }
-
-    private int verifyInjectionValue(String value) {
-        try {
-            final int parsed = Integer.parseInt(value);
-
-            switch (parsed) {
-                case ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT:
-                    return ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT;
-                case ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED:
-                    return ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED;
-            }
-        } catch (NumberFormatException e) {
-            // Do nothing.
-        }
-
-        return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
-    }
-
-    /**
-     * @return The URL for injecting the screen reader.
-     */
-    private String getScreenReaderInjectionUrl() {
-        final String screenReaderUrl = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
-        return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
-    }
-
-    /**
-     * @return {@code true} if JavaScript is enabled in the {@link WebView}
-     *         settings.
-     */
-    private boolean isJavaScriptEnabled() {
-        final WebSettings settings = mWebView.getSettings();
-        if (settings == null) {
-            return false;
-        }
-
-        return settings.getJavaScriptEnabled();
-    }
-
-    /**
-     * @return {@code true} if accessibility is enabled.
-     */
-    private boolean isAccessibilityEnabled() {
-        return mAccessibilityManager.isEnabled();
-    }
-
-    /**
-     * Packs an accessibility action into a JSON object and sends it to AndroidVox.
-     *
-     * @param action The action identifier.
-     * @param arguments The action arguments, if applicable.
-     * @return The result of the action.
-     */
-    private boolean sendActionToAndroidVox(int action, Bundle arguments) {
-        if (mAccessibilityJSONObject == null) {
-            mAccessibilityJSONObject = new JSONObject();
-        } else {
-            // Remove all keys from the object.
-            final Iterator<?> keys = mAccessibilityJSONObject.keys();
-            while (keys.hasNext()) {
-                keys.next();
-                keys.remove();
-            }
-        }
-
-        try {
-            mAccessibilityJSONObject.accumulate("action", action);
-
-            switch (action) {
-                case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-                case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-                    if (arguments != null) {
-                        final int granularity = arguments.getInt(
-                                AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
-                        mAccessibilityJSONObject.accumulate("granularity", granularity);
-                    }
-                    break;
-                case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-                case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-                    if (arguments != null) {
-                        final String element = arguments.getString(
-                                AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
-                        mAccessibilityJSONObject.accumulate("element", element);
-                    }
-                    break;
-            }
-        } catch (JSONException e) {
-            return false;
-        }
-
-        final String jsonString = mAccessibilityJSONObject.toString();
-        final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString);
-        return mCallback.performAction(mWebView, jsCode);
-    }
-
-    /**
-     * Used to protect the TextToSpeech class, only exposing the methods we want to expose.
-     */
-    private static class TextToSpeechWrapper {
-        private static final String WRAP_TAG = TextToSpeechWrapper.class.getSimpleName();
-
-        /** Lock used to control access to the TextToSpeech object. */
-        private final Object mTtsLock = new Object();
-
-        private final HashMap<String, String> mTtsParams;
-        private final TextToSpeech mTextToSpeech;
-
-        /**
-         * Whether this wrapper is ready to speak. If this is {@code true} then
-         * {@link #mShutdown} is guaranteed to be {@code false}.
-         */
-        private volatile boolean mReady;
-
-        /**
-         * Whether this wrapper was shut down. If this is {@code true} then
-         * {@link #mReady} is guaranteed to be {@code false}.
-         */
-        private volatile boolean mShutdown;
-
-        public TextToSpeechWrapper(Context context) {
-            if (DEBUG) {
-                Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread "
-                        + Thread.currentThread().getId() + "...");
-            }
-
-            final String pkgName = context.getPackageName();
-
-            mReady = false;
-            mShutdown = false;
-
-            mTtsParams = new HashMap<String, String>();
-            mTtsParams.put(Engine.KEY_PARAM_UTTERANCE_ID, WRAP_TAG);
-
-            mTextToSpeech = new TextToSpeech(
-                    context, mInitListener, null, pkgName + ".**webview**", true);
-            mTextToSpeech.setOnUtteranceProgressListener(mErrorListener);
-        }
-
-        @JavascriptInterface
-        @SuppressWarnings("unused")
-        public boolean isSpeaking() {
-            synchronized (mTtsLock) {
-                if (!mReady) {
-                    return false;
-                }
-
-                return mTextToSpeech.isSpeaking();
-            }
-        }
-
-        @JavascriptInterface
-        @SuppressWarnings("unused")
-        public int speak(String text, int queueMode, HashMap<String, String> params) {
-            synchronized (mTtsLock) {
-                if (!mReady) {
-                    if (DEBUG) {
-                        Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init");
-                    }
-                    return TextToSpeech.ERROR;
-                } else {
-                    if (DEBUG) {
-                        Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder");
-                    }
-                }
-
-                return mTextToSpeech.speak(text, queueMode, params);
-            }
-        }
-
-        @JavascriptInterface
-        @SuppressWarnings("unused")
-        public int stop() {
-            synchronized (mTtsLock) {
-                if (!mReady) {
-                    if (DEBUG) {
-                        Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize");
-                    }
-                    return TextToSpeech.ERROR;
-                } else {
-                    if (DEBUG) {
-                        Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder");
-                    }
-                }
-
-                return mTextToSpeech.stop();
-            }
-        }
-
-        @SuppressWarnings("unused")
-        protected void shutdown() {
-            synchronized (mTtsLock) {
-                if (!mReady) {
-                    if (DEBUG) {
-                        Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize");
-                    }
-                } else {
-                    if (DEBUG) {
-                        Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from "
-                                + "thread " + Thread.currentThread().getId() + "...");
-                    }
-                }
-                mShutdown = true;
-                mReady = false;
-                mTextToSpeech.shutdown();
-            }
-        }
-
-        private final OnInitListener mInitListener = new OnInitListener() {
-            @Override
-            public void onInit(int status) {
-                synchronized (mTtsLock) {
-                    if (!mShutdown && (status == TextToSpeech.SUCCESS)) {
-                        if (DEBUG) {
-                            Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
-                                    + "] Initialized successfully");
-                        }
-                        mReady = true;
-                    } else {
-                        if (DEBUG) {
-                            Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
-                                    + "] Failed to initialize");
-                        }
-                        mReady = false;
-                    }
-                }
-            }
-        };
-
-        private final UtteranceProgressListener mErrorListener = new UtteranceProgressListener() {
-            @Override
-            public void onStart(String utteranceId) {
-                // Do nothing.
-            }
-
-            @Override
-            public void onError(String utteranceId) {
-                if (DEBUG) {
-                    Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
-                            + "] Failed to speak utterance");
-                }
-            }
-
-            @Override
-            public void onDone(String utteranceId) {
-                // Do nothing.
-            }
-        };
-    }
-
-    /**
-     * Exposes result interface to JavaScript.
-     */
-    private static class CallbackHandler {
-        private static final String JAVASCRIPT_ACTION_TEMPLATE =
-                "javascript:(function() { %s.onResult(%d, %s); })();";
-
-        // Time in milliseconds to wait for a result before failing.
-        private static final long RESULT_TIMEOUT = 5000;
-
-        private final AtomicInteger mResultIdCounter = new AtomicInteger();
-        private final Object mResultLock = new Object();
-        private final String mInterfaceName;
-        private final Handler mMainHandler;
-
-        private Runnable mCallbackRunnable;
-
-        private boolean mResult = false;
-        private int mResultId = -1;
-
-        private CallbackHandler(String interfaceName) {
-            mInterfaceName = interfaceName;
-            mMainHandler = new Handler();
-        }
-
-        /**
-         * Performs an action and attempts to wait for a result.
-         *
-         * @param webView The WebView to perform the action on.
-         * @param code JavaScript code that evaluates to a result.
-         * @return The result of the action, or false if it timed out.
-         */
-        private boolean performAction(WebView webView, String code) {
-            final int resultId = mResultIdCounter.getAndIncrement();
-            final String url = String.format(
-                    JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId, code);
-            webView.loadUrl(url);
-
-            return getResultAndClear(resultId);
-        }
-
-        /**
-         * Gets the result of a request to perform an accessibility action.
-         *
-         * @param resultId The result id to match the result with the request.
-         * @return The result of the request.
-         */
-        private boolean getResultAndClear(int resultId) {
-            synchronized (mResultLock) {
-                final boolean success = waitForResultTimedLocked(resultId);
-                final boolean result = success ? mResult : false;
-                clearResultLocked();
-                return result;
-            }
-        }
-
-        /**
-         * Clears the result state.
-         */
-        private void clearResultLocked() {
-            mResultId = -1;
-            mResult = false;
-        }
-
-        /**
-         * Waits up to a given bound for a result of a request and returns it.
-         *
-         * @param resultId The result id to match the result with the request.
-         * @return Whether the result was received.
-         */
-        private boolean waitForResultTimedLocked(int resultId) {
-            final long startTimeMillis = SystemClock.uptimeMillis();
-
-            if (DEBUG) {
-                Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "...");
-            }
-
-            while (true) {
-                // Fail if we received a callback from the future.
-                if (mResultId > resultId) {
-                    if (DEBUG) {
-                        Log.w(TAG, "Aborted CVOX result");
-                    }
-                    return false;
-                }
-
-                final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis);
-
-                // Succeed if we received the callback we were expecting.
-                if (DEBUG) {
-                    Log.w(TAG, "Check " + mResultId + " versus expected " + resultId);
-                }
-                if (mResultId == resultId) {
-                    if (DEBUG) {
-                        Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms");
-                    }
-                    return true;
-                }
-
-                final long waitTimeMillis = (RESULT_TIMEOUT - elapsedTimeMillis);
-
-                // Fail if we've already exceeded the timeout.
-                if (waitTimeMillis <= 0) {
-                    if (DEBUG) {
-                        Log.w(TAG, "Timed out while waiting for CVOX result");
-                    }
-                    return false;
-                }
-
-                try {
-                    if (DEBUG) {
-                        Log.w(TAG, "Start waiting...");
-                    }
-                    mResultLock.wait(waitTimeMillis);
-                } catch (InterruptedException ie) {
-                    if (DEBUG) {
-                        Log.w(TAG, "Interrupted while waiting for CVOX result");
-                    }
-                }
-            }
-        }
-
-        /**
-         * Callback exposed to JavaScript. Handles returning the result of a
-         * request to a waiting (or potentially timed out) thread.
-         *
-         * @param id The result id of the request as a {@link String}.
-         * @param result The result of the request as a {@link String}.
-         */
-        @JavascriptInterface
-        @SuppressWarnings("unused")
-        public void onResult(String id, String result) {
-            if (DEBUG) {
-                Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id);
-            }
-            final int resultId;
-
-            try {
-                resultId = Integer.parseInt(id);
-            } catch (NumberFormatException e) {
-                return;
-            }
-
-            synchronized (mResultLock) {
-                if (resultId > mResultId) {
-                    mResult = Boolean.parseBoolean(result);
-                    mResultId = resultId;
-                } else {
-                    if (DEBUG) {
-                        Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId);
-                    }
-                }
-                mResultLock.notifyAll();
-            }
-        }
-
-        /**
-         * Requests a callback to ensure that the JavaScript interface for this
-         * object has been added successfully.
-         *
-         * @param webView The web view to request a callback from.
-         * @param callbackRunnable Runnable to execute if a callback is received.
-         */
-        public void requestCallback(WebView webView, Runnable callbackRunnable) {
-            mCallbackRunnable = callbackRunnable;
-
-            webView.loadUrl("javascript:(function() { " + mInterfaceName + ".callback(); })();");
-        }
-
-        @JavascriptInterface
-        @SuppressWarnings("unused")
-        public void callback() {
-            if (mCallbackRunnable != null) {
-                mMainHandler.post(mCallbackRunnable);
-                mCallbackRunnable = null;
-            }
-        }
-    }
-}
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
deleted file mode 100644
index 40cc4e9..0000000
--- a/core/java/android/webkit/AccessibilityInjectorFallback.java
+++ /dev/null
@@ -1,636 +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.
- */
-
-package android.webkit;
-
-import android.os.Bundle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.webkit.WebViewCore.EventHub;
-
-import com.android.internal.os.SomeArgs;
-
-import java.util.ArrayList;
-
-/**
- * This class injects accessibility into WebViews with disabled JavaScript or
- * WebViews with enabled JavaScript but for which we have no accessibility
- * script to inject.
- * </p>
- * Note: To avoid changes in the framework upon changing the available
- *       navigation axis, or reordering the navigation axis, or changing
- *       the key bindings, or defining sequence of actions to be bound to
- *       a given key this class is navigation axis agnostic. It is only
- *       aware of one navigation axis which is in fact the default behavior
- *       of webViews while using the DPAD/TrackBall.
- * </p>
- * In general a key binding is a mapping from modifiers + key code to
- * a sequence of actions. For more detail how to specify key bindings refer to
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
- * </p>
- * The possible actions are invocations to
- * {@link #setCurrentAxis(int, boolean, String)}, or
- * {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
- * {@link #performAxisTransition(int, int, boolean, String)}
- * referred via the values of:
- * {@link #ACTION_SET_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
- * {@link #ACTION_PERFORM_AXIS_TRANSITION},
- * respectively.
- * The arguments for the action invocation are specified as offset
- * hexademical pairs. Note the last argument of the invocation
- * should NOT be specified in the binding as it is provided by
- * this class. For details about the key binding implementation
- * refer to {@link AccessibilityWebContentKeyBinding}.
- */
-class AccessibilityInjectorFallback {
-    private static final String LOG_TAG = "AccessibilityInjector";
-
-    private static final boolean DEBUG = true;
-
-    private static final int ACTION_SET_CURRENT_AXIS = 0;
-    private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
-    private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
-    private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
-    private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
-
-    /** Timeout after which asynchronous granular movement is aborted. */
-    private static final int MODIFY_SELECTION_TIMEOUT = 500;
-
-    // WebView navigation axes from WebViewCore.h, plus an additional axis for
-    // the default behavior.
-    private static final int NAVIGATION_AXIS_CHARACTER = 0;
-    private static final int NAVIGATION_AXIS_WORD = 1;
-    private static final int NAVIGATION_AXIS_SENTENCE = 2;
-    @SuppressWarnings("unused")
-    private static final int NAVIGATION_AXIS_HEADING = 3;
-    @SuppressWarnings("unused")
-    private static final int NAVIGATION_AXIS_SIBLING = 4;
-    @SuppressWarnings("unused")
-    private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
-    private static final int NAVIGATION_AXIS_DOCUMENT = 6;
-    private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
-
-    // WebView navigation directions from WebViewCore.h.
-    private static final int NAVIGATION_DIRECTION_BACKWARD = 0;
-    private static final int NAVIGATION_DIRECTION_FORWARD = 1;
-
-    // these are the same for all instances so make them process wide
-    private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
-        new ArrayList<AccessibilityWebContentKeyBinding>();
-
-    // handle to the WebViewClassic this injector is associated with.
-    private final WebViewClassic mWebView;
-    private final WebView mWebViewInternal;
-
-    // Event scheduled for sending as soon as we receive the selected text.
-    private AccessibilityEvent mScheduledEvent;
-
-    // Token required to send the scheduled event.
-    private int mScheduledToken = 0;
-
-    // the current traversal axis
-    private int mCurrentAxis = 2; // sentence
-
-    // we need to consume the up if we have handled the last down
-    private boolean mLastDownEventHandled;
-
-    // getting two empty selection strings in a row we let the WebView handle the event
-    private boolean mIsLastSelectionStringNull;
-
-    // keep track of last direction
-    private int mLastDirection;
-
-    // Lock used for asynchronous selection callback.
-    private final Object mCallbackLock = new Object();
-
-    // Whether the asynchronous selection callback was received.
-    private boolean mCallbackReceived;
-
-    // Whether the asynchronous selection callback succeeded.
-    private boolean mCallbackResult;
-
-    /**
-     * Creates a new injector associated with a given {@link WebViewClassic}.
-     *
-     * @param webView The associated WebViewClassic.
-     */
-    public AccessibilityInjectorFallback(WebViewClassic webView) {
-        mWebView = webView;
-        mWebViewInternal = mWebView.getWebView();
-        ensureWebContentKeyBindings();
-    }
-
-    /**
-     * Processes a key down <code>event</code>.
-     *
-     * @return True if the event was processed.
-     */
-    public boolean onKeyEvent(KeyEvent event) {
-        // We do not handle ENTER in any circumstances.
-        if (isEnterActionKey(event.getKeyCode())) {
-            return false;
-        }
-
-        if (event.getAction() == KeyEvent.ACTION_UP) {
-            return mLastDownEventHandled;
-        }
-
-        mLastDownEventHandled = false;
-
-        AccessibilityWebContentKeyBinding binding = null;
-        for (AccessibilityWebContentKeyBinding candidate : sBindings) {
-            if (event.getKeyCode() == candidate.getKeyCode()
-                    && event.hasModifiers(candidate.getModifiers())) {
-                binding = candidate;
-                break;
-            }
-        }
-
-        if (binding == null) {
-            return false;
-        }
-
-        for (int i = 0, count = binding.getActionCount(); i < count; i++) {
-            int actionCode = binding.getActionCode(i);
-            String contentDescription = Integer.toHexString(binding.getAction(i));
-            switch (actionCode) {
-                case ACTION_SET_CURRENT_AXIS:
-                    int axis = binding.getFirstArgument(i);
-                    boolean sendEvent = (binding.getSecondArgument(i) == 1);
-                    setCurrentAxis(axis, sendEvent, contentDescription);
-                    mLastDownEventHandled = true;
-                    break;
-                case ACTION_TRAVERSE_CURRENT_AXIS:
-                    int direction = binding.getFirstArgument(i);
-                    // on second null selection string in same direction - WebView handles the event
-                    if (direction == mLastDirection && mIsLastSelectionStringNull) {
-                        mIsLastSelectionStringNull = false;
-                        return false;
-                    }
-                    mLastDirection = direction;
-                    sendEvent = (binding.getSecondArgument(i) == 1);
-                    mLastDownEventHandled = traverseGivenAxis(
-                            direction, mCurrentAxis, sendEvent, contentDescription, false);
-                    break;
-                case ACTION_TRAVERSE_GIVEN_AXIS:
-                    direction = binding.getFirstArgument(i);
-                    // on second null selection string in same direction => WebView handle the event
-                    if (direction == mLastDirection && mIsLastSelectionStringNull) {
-                        mIsLastSelectionStringNull = false;
-                        return false;
-                    }
-                    mLastDirection = direction;
-                    axis =  binding.getSecondArgument(i);
-                    sendEvent = (binding.getThirdArgument(i) == 1);
-                    traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
-                    mLastDownEventHandled = true;
-                    break;
-                case ACTION_PERFORM_AXIS_TRANSITION:
-                    int fromAxis = binding.getFirstArgument(i);
-                    int toAxis = binding.getSecondArgument(i);
-                    sendEvent = (binding.getThirdArgument(i) == 1);
-                    performAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
-                    mLastDownEventHandled = true;
-                    break;
-                case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
-                    // This is a special case since we treat the default WebView navigation
-                    // behavior as one of the possible navigation axis the user can use.
-                    // If we are not on the default WebView navigation axis this is NOP.
-                    if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
-                        // While WebVew handles navigation we do not get null selection
-                        // strings so do not check for that here as the cases above.
-                        mLastDirection = binding.getFirstArgument(i);
-                        sendEvent = (binding.getSecondArgument(i) == 1);
-                        traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
-                            sendEvent, contentDescription, false);
-                        mLastDownEventHandled = false;
-                    } else {
-                        mLastDownEventHandled = true;
-                    }
-                    break;
-                default:
-                    Log.w(LOG_TAG, "Unknown action code: " + actionCode);
-            }
-        }
-
-        return mLastDownEventHandled;
-    }
-
-    /**
-     * Set the current navigation axis.
-     *
-     * @param axis The axis to set.
-     * @param sendEvent Whether to send an accessibility event to
-     *        announce the change.
-     */
-    private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
-        mCurrentAxis = axis;
-        if (sendEvent) {
-            final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
-                    AccessibilityEvent.TYPE_ANNOUNCEMENT);
-            event.getText().add(String.valueOf(axis));
-            event.setContentDescription(contentDescription);
-            sendAccessibilityEvent(event);
-        }
-    }
-
-    /**
-     * Performs conditional transition one axis to another.
-     *
-     * @param fromAxis The axis which must be the current for the transition to occur.
-     * @param toAxis The axis to which to transition.
-     * @param sendEvent Flag if to send an event to announce successful transition.
-     * @param contentDescription A description of the performed action.
-     */
-    private void performAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
-            String contentDescription) {
-        if (mCurrentAxis == fromAxis) {
-            setCurrentAxis(toAxis, sendEvent, contentDescription);
-        }
-    }
-
-    boolean performAccessibilityAction(int action, Bundle arguments) {
-        switch (action) {
-            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
-                final int direction = getDirectionForAction(action);
-                final int axis = getAxisForGranularity(arguments.getInt(
-                        AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
-                return traverseGivenAxis(direction, axis, true, null, true);
-            }
-            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
-                final int direction = getDirectionForAction(action);
-                // TODO: Add support for moving by object.
-                final int axis = NAVIGATION_AXIS_SENTENCE;
-                return traverseGivenAxis(direction, axis, true, null, true);
-            }
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * Returns the {@link WebView}-defined direction for the given
-     * {@link AccessibilityNodeInfo}-defined action.
-     *
-     * @param action An accessibility action identifier.
-     * @return A web view navigation direction.
-     */
-    private static int getDirectionForAction(int action) {
-        switch (action) {
-            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-            case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-                return NAVIGATION_DIRECTION_FORWARD;
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-                return NAVIGATION_DIRECTION_BACKWARD;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Returns the {@link WebView}-defined axis for the given
-     * {@link AccessibilityNodeInfo}-defined granularity.
-     *
-     * @param granularity An accessibility granularity identifier.
-     * @return A web view navigation axis.
-     */
-    private static int getAxisForGranularity(int granularity) {
-        switch (granularity) {
-            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
-                return NAVIGATION_AXIS_CHARACTER;
-            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
-                return NAVIGATION_AXIS_WORD;
-            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
-                return NAVIGATION_AXIS_SENTENCE;
-            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
-                // TODO: This should map to object once we implement it.
-                return NAVIGATION_AXIS_SENTENCE;
-            case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
-                return NAVIGATION_AXIS_DOCUMENT;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Traverse the document along the given navigation axis.
-     *
-     * @param direction The direction of traversal.
-     * @param axis The axis along which to traverse.
-     * @param sendEvent Whether to send an accessibility event to
-     *        announce the change.
-     * @param contentDescription A description of the performed action.
-     */
-    private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
-            String contentDescription, boolean sychronous) {
-        final WebViewCore webViewCore = mWebView.getWebViewCore();
-        if (webViewCore == null) {
-            return false;
-        }
-
-        if (sendEvent) {
-            final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
-                    AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
-            // The text will be set upon receiving the selection string.
-            event.setContentDescription(contentDescription);
-            mScheduledEvent = event;
-            mScheduledToken++;
-        }
-
-        // if the axis is the default let WebView handle the event which will
-        // result in cursor ring movement and selection of its content
-        if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
-            return false;
-        }
-
-        final SomeArgs args = SomeArgs.obtain();
-        args.argi1 = direction;
-        args.argi2 = axis;
-        args.argi3 = mScheduledToken;
-
-        // If we don't need synchronous results, just return true.
-        if (!sychronous) {
-            webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
-            return true;
-        }
-
-        final boolean callbackResult;
-
-        synchronized (mCallbackLock) {
-            mCallbackReceived = false;
-
-            // Asynchronously changes the selection in WebView, which responds by
-            // calling onSelectionStringChanged().
-            webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
-
-            try {
-                mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
-            } catch (InterruptedException e) {
-                // Do nothing.
-            }
-
-            callbackResult = mCallbackResult;
-        }
-
-        return (mCallbackReceived && callbackResult);
-    }
-
-    /* package */ void onSelectionStringChangedWebCoreThread(
-            final String selection, final int token) {
-        synchronized (mCallbackLock) {
-            mCallbackReceived = true;
-            mCallbackResult = (selection != null);
-            mCallbackLock.notifyAll();
-        }
-
-        // Managing state and sending events must take place on the UI thread.
-        mWebViewInternal.post(new Runnable() {
-            @Override
-            public void run() {
-                onSelectionStringChangedMainThread(selection, token);
-            }
-        });
-    }
-
-    private void onSelectionStringChangedMainThread(String selection, int token) {
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Selection string: " + selection);
-        }
-
-        if (token != mScheduledToken) {
-            if (DEBUG) {
-                Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
-            }
-            return;
-        }
-
-        mIsLastSelectionStringNull = (selection == null);
-
-        final AccessibilityEvent event = mScheduledEvent;
-        mScheduledEvent = null;
-
-        if ((event != null) && (selection != null)) {
-            event.getText().add(selection);
-            event.setFromIndex(0);
-            event.setToIndex(selection.length());
-            sendAccessibilityEvent(event);
-        }
-    }
-
-    /**
-     * Sends an {@link AccessibilityEvent}.
-     *
-     * @param event The event to send.
-     */
-    private void sendAccessibilityEvent(AccessibilityEvent event) {
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Dispatching: " + event);
-        }
-        // accessibility may be disabled while waiting for the selection string
-        AccessibilityManager accessibilityManager =
-            AccessibilityManager.getInstance(mWebView.getContext());
-        if (accessibilityManager.isEnabled()) {
-            accessibilityManager.sendAccessibilityEvent(event);
-        }
-    }
-
-    /**
-     * @return An accessibility event whose members are populated except its
-     *         text and content description.
-     */
-    private AccessibilityEvent getPartialyPopulatedAccessibilityEvent(int eventType) {
-        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        mWebViewInternal.onInitializeAccessibilityEvent(event);
-        return event;
-    }
-
-    /**
-     * Ensures that the Web content key bindings are loaded.
-     */
-    private void ensureWebContentKeyBindings() {
-        if (sBindings.size() > 0) {
-            return;
-        }
-
-        String webContentKeyBindingsString  = Settings.Secure.getString(
-                mWebView.getContext().getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
-
-        SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
-        semiColonSplitter.setString(webContentKeyBindingsString);
-
-        while (semiColonSplitter.hasNext()) {
-            String bindingString = semiColonSplitter.next();
-            if (TextUtils.isEmpty(bindingString)) {
-                Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
-                        + webContentKeyBindingsString);
-                continue;
-            }
-            String[] keyValueArray = bindingString.split("=");
-            if (keyValueArray.length != 2) {
-                Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
-                continue;
-            }
-            try {
-                long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
-                String[] actionStrings = keyValueArray[1].split(":");
-                int[] actions = new int[actionStrings.length];
-                for (int i = 0, count = actions.length; i < count; i++) {
-                    actions[i] = Integer.decode(actionStrings[i].trim());
-                }
-                sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
-            } catch (NumberFormatException nfe) {
-                Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
-            }
-        }
-    }
-
-    private boolean isEnterActionKey(int keyCode) {
-        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
-                || keyCode == KeyEvent.KEYCODE_ENTER
-                || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
-    }
-
-    /**
-     * Represents a web content key-binding.
-     */
-    private static final class AccessibilityWebContentKeyBinding {
-
-        private static final int MODIFIERS_OFFSET = 32;
-        private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
-
-        private static final int KEY_CODE_OFFSET = 0;
-        private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
-
-        private static final int ACTION_OFFSET = 24;
-        private static final int ACTION_MASK = 0xFF000000;
-
-        private static final int FIRST_ARGUMENT_OFFSET = 16;
-        private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
-
-        private static final int SECOND_ARGUMENT_OFFSET = 8;
-        private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
-
-        private static final int THIRD_ARGUMENT_OFFSET = 0;
-        private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
-
-        private final long mKeyCodeAndModifiers;
-
-        private final int [] mActionSequence;
-
-        /**
-         * @return The key code of the binding key.
-         */
-        public int getKeyCode() {
-            return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
-        }
-
-        /**
-         * @return The meta state of the binding key.
-         */
-        public int getModifiers() {
-            return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
-        }
-
-        /**
-         * @return The number of actions in the key binding.
-         */
-        public int getActionCount() {
-            return mActionSequence.length;
-        }
-
-        /**
-         * @param index The action for a given action <code>index</code>.
-         */
-        public int getAction(int index) {
-            return mActionSequence[index];
-        }
-
-        /**
-         * @param index The action code for a given action <code>index</code>.
-         */
-        public int getActionCode(int index) {
-            return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
-        }
-
-        /**
-         * @param index The first argument for a given action <code>index</code>.
-         */
-        public int getFirstArgument(int index) {
-            return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
-        }
-
-        /**
-         * @param index The second argument for a given action <code>index</code>.
-         */
-        public int getSecondArgument(int index) {
-            return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
-        }
-
-        /**
-         * @param index The third argument for a given action <code>index</code>.
-         */
-        public int getThirdArgument(int index) {
-            return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
-        }
-
-        /**
-         * Creates a new instance.
-         * @param keyCodeAndModifiers The key for the binding (key and modifiers).
-         * @param actionSequence The sequence of action for the binding.
-         */
-        public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
-            mKeyCodeAndModifiers = keyCodeAndModifiers;
-            mActionSequence = actionSequence;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            builder.append("modifiers: ");
-            builder.append(getModifiers());
-            builder.append(", keyCode: ");
-            builder.append(getKeyCode());
-            builder.append(", actions[");
-            for (int i = 0, count = getActionCount(); i < count; i++) {
-                builder.append("{actionCode");
-                builder.append(i);
-                builder.append(": ");
-                builder.append(getActionCode(i));
-                builder.append(", firstArgument: ");
-                builder.append(getFirstArgument(i));
-                builder.append(", secondArgument: ");
-                builder.append(getSecondArgument(i));
-                builder.append(", thirdArgument: ");
-                builder.append(getThirdArgument(i));
-                builder.append("}");
-            }
-            builder.append("]");
-            return builder.toString();
-        }
-    }
-}
diff --git a/core/java/android/webkit/AutoCompletePopup.java b/core/java/android/webkit/AutoCompletePopup.java
deleted file mode 100644
index c624ce4..0000000
--- a/core/java/android/webkit/AutoCompletePopup.java
+++ /dev/null
@@ -1,251 +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;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.text.Editable;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.AbsoluteLayout;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.Filter;
-import android.widget.Filterable;
-import android.widget.ListAdapter;
-import android.widget.ListPopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
-
-class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener,
-        OnDismissListener{
-    private static class AnchorView extends View {
-        AnchorView(Context context) {
-            super(context);
-            setFocusable(false);
-            setVisibility(INVISIBLE);
-        }
-    }
-    private static final int AUTOFILL_FORM = 100;
-    private boolean mIsAutoFillProfileSet;
-    private Handler mHandler;
-    private int mQueryId;
-    private ListPopupWindow mPopup;
-    private Filter mFilter;
-    private CharSequence mText;
-    private ListAdapter mAdapter;
-    private View mAnchor;
-    private WebViewClassic.WebViewInputConnection mInputConnection;
-    private WebViewClassic mWebView;
-
-    public AutoCompletePopup(WebViewClassic webView,
-            WebViewClassic.WebViewInputConnection inputConnection) {
-        mInputConnection = inputConnection;
-        mWebView = webView;
-        mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                case AUTOFILL_FORM:
-                    mWebView.autoFillForm(mQueryId);
-                    break;
-                }
-            }
-        };
-    }
-
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        if (mPopup == null) {
-            return false;
-        }
-        if (keyCode == KeyEvent.KEYCODE_BACK && mPopup.isShowing()) {
-            // special case for the back key, we do not even try to send it
-            // to the drop down list but instead, consume it immediately
-            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
-                KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
-                if (state != null) {
-                    state.startTracking(event, this);
-                }
-                return true;
-            } else if (event.getAction() == KeyEvent.ACTION_UP) {
-                KeyEvent.DispatcherState state = mAnchor.getKeyDispatcherState();
-                if (state != null) {
-                    state.handleUpEvent(event);
-                }
-                if (event.isTracking() && !event.isCanceled()) {
-                    mPopup.dismiss();
-                    return true;
-                }
-            }
-        }
-        if (mPopup.isShowing()) {
-            return mPopup.onKeyPreIme(keyCode, event);
-        }
-        return false;
-    }
-
-    public void setText(CharSequence text) {
-        mText = text;
-        if (mFilter != null) {
-            mFilter.filter(text, this);
-        }
-    }
-
-    public void setAutoFillQueryId(int queryId) {
-        mQueryId = queryId;
-    }
-
-    public void clearAdapter() {
-        mAdapter = null;
-        mFilter = null;
-        if (mPopup != null) {
-            mPopup.dismiss();
-            mPopup.setAdapter(null);
-        }
-    }
-
-    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
-        ensurePopup();
-        mPopup.setAdapter(adapter);
-        mAdapter = adapter;
-        if (adapter != null) {
-            mFilter = adapter.getFilter();
-            mFilter.filter(mText, this);
-        } else {
-            mFilter = null;
-        }
-        resetRect();
-    }
-
-    public void resetRect() {
-        ensurePopup();
-        int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left);
-        int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right);
-        int width = right - left;
-        mPopup.setWidth(width);
-
-        int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom);
-        int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top);
-        int height = bottom - top;
-
-        AbsoluteLayout.LayoutParams lp =
-                (AbsoluteLayout.LayoutParams) mAnchor.getLayoutParams();
-        boolean needsUpdate = false;
-        if (null == lp) {
-            lp = new AbsoluteLayout.LayoutParams(width, height, left, top);
-        } else {
-            if ((lp.x != left) || (lp.y != top) || (lp.width != width)
-                    || (lp.height != height)) {
-                needsUpdate = true;
-                lp.x = left;
-                lp.y = top;
-                lp.width = width;
-                lp.height = height;
-            }
-        }
-        if (needsUpdate) {
-            mAnchor.setLayoutParams(lp);
-        }
-        if (mPopup.isShowing()) {
-            mPopup.show(); // update its position
-        }
-    }
-
-    // AdapterView.OnItemClickListener implementation
-    @Override
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        if (mPopup == null) {
-            return;
-        }
-        if (id == 0 && position == 0 && mInputConnection.getIsAutoFillable()) {
-            mText = "";
-            pushTextToInputConnection();
-            // Blank out the text box while we wait for WebCore to fill the form.
-            if (mIsAutoFillProfileSet) {
-                // Call a webview method to tell WebCore to autofill the form.
-                mWebView.autoFillForm(mQueryId);
-            } else {
-                // There is no autofill profile setup yet and the user has
-                // elected to try and set one up. Call through to the
-                // embedder to action that.
-                WebChromeClient webChromeClient = mWebView.getWebChromeClient();
-                if (webChromeClient != null) {
-                    webChromeClient.setupAutoFill(
-                        mHandler.obtainMessage(AUTOFILL_FORM));
-                }
-            }
-        } else {
-            Object selectedItem;
-            if (position < 0) {
-                selectedItem = mPopup.getSelectedItem();
-            } else {
-                selectedItem = mAdapter.getItem(position);
-            }
-            if (selectedItem != null) {
-                setText(mFilter.convertResultToString(selectedItem));
-                pushTextToInputConnection();
-            }
-        }
-        mPopup.dismiss();
-    }
-
-    public void setIsAutoFillProfileSet(boolean isAutoFillProfileSet) {
-        mIsAutoFillProfileSet = isAutoFillProfileSet;
-    }
-
-    private void pushTextToInputConnection() {
-        Editable oldText = mInputConnection.getEditable();
-        mInputConnection.setSelection(0, oldText.length());
-        mInputConnection.replaceSelection(mText);
-        mInputConnection.setSelection(mText.length(), mText.length());
-    }
-
-    @Override
-    public void onFilterComplete(int count) {
-        ensurePopup();
-        boolean showDropDown = (count > 0) &&
-                (mInputConnection.getIsAutoFillable() || mText.length() > 0);
-        if (showDropDown) {
-            if (!mPopup.isShowing()) {
-                // Make sure the list does not obscure the IME when shown for the first time.
-                mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
-            }
-            mPopup.show();
-            mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
-        } else {
-            mPopup.dismiss();
-        }
-    }
-
-    @Override
-    public void onDismiss() {
-        mWebView.getWebView().removeView(mAnchor);
-    }
-
-    private void ensurePopup() {
-        if (mPopup == null) {
-            mPopup = new ListPopupWindow(mWebView.getContext());
-            mAnchor = new AnchorView(mWebView.getContext());
-            mWebView.getWebView().addView(mAnchor);
-            mPopup.setOnItemClickListener(this);
-            mPopup.setAnchorView(mAnchor);
-            mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
-        } else if (mWebView.getWebView().indexOfChild(mAnchor) < 0) {
-            mWebView.getWebView().addView(mAnchor);
-        }
-    }
-}
-
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
deleted file mode 100644
index 6955d14..0000000
--- a/core/java/android/webkit/BrowserFrame.java
+++ /dev/null
@@ -1,1351 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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;
-
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.graphics.Bitmap;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.net.http.ErrorStrings;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Surface;
-import android.view.ViewRootImpl;
-import android.view.WindowManager;
-
-import junit.framework.Assert;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URLEncoder;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import com.android.org.conscrypt.OpenSSLKey;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
-class BrowserFrame extends Handler {
-
-    private static final String LOGTAG = "webkit";
-
-    /**
-     * Cap the number of LoadListeners that will be instantiated, so
-     * we don't blow the GREF count.  Attempting to queue more than
-     * this many requests will prompt an error() callback on the
-     * request's LoadListener
-     */
-    private final static int MAX_OUTSTANDING_REQUESTS = 300;
-
-    private final CallbackProxy mCallbackProxy;
-    private final WebSettingsClassic mSettings;
-    private final Context mContext;
-    private final WebViewDatabaseClassic mDatabase;
-    private final WebViewCore mWebViewCore;
-    /* package */ boolean mLoadInitFromJava;
-    private int mLoadType;
-    private boolean mFirstLayoutDone = true;
-    private boolean mCommitted = true;
-    // Flag for blocking messages. This is used during destroy() so
-    // that if the UI thread posts any messages after the message
-    // queue has been cleared,they are ignored.
-    private boolean mBlockMessages = false;
-    private int mOrientation = -1;
-
-    // Is this frame the main frame?
-    private boolean mIsMainFrame;
-
-    // Javascript interface object
-    private class JSObject {
-        Object object;
-        boolean requireAnnotation;
-
-        public JSObject(Object object, boolean requireAnnotation) {
-            this.object = object;
-            this.requireAnnotation = requireAnnotation;
-        }
-    }
-
-    // Attached Javascript interfaces
-    private Map<String, JSObject> mJavaScriptObjects;
-    private Set<Object> mRemovedJavaScriptObjects;
-
-    // Key store handler when Chromium HTTP stack is used.
-    private KeyStoreHandler mKeyStoreHandler = null;
-
-    // message ids
-    // a message posted when a frame loading is completed
-    static final int FRAME_COMPLETED = 1001;
-    // orientation change message
-    static final int ORIENTATION_CHANGED = 1002;
-    // a message posted when the user decides the policy
-    static final int POLICY_FUNCTION = 1003;
-
-    // Note: need to keep these in sync with FrameLoaderTypes.h in native
-    static final int FRAME_LOADTYPE_STANDARD = 0;
-    static final int FRAME_LOADTYPE_BACK = 1;
-    static final int FRAME_LOADTYPE_FORWARD = 2;
-    static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
-    static final int FRAME_LOADTYPE_RELOAD = 4;
-    static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
-    static final int FRAME_LOADTYPE_SAME = 6;
-    static final int FRAME_LOADTYPE_REDIRECT = 7;
-    static final int FRAME_LOADTYPE_REPLACE = 8;
-
-    // A progress threshold to switch from history Picture to live Picture
-    private static final int TRANSITION_SWITCH_THRESHOLD = 75;
-
-    // This is a field accessed by native code as well as package classes.
-    /*package*/ int mNativeFrame;
-
-    // Static instance of a JWebCoreJavaBridge to handle timer and cookie
-    // requests from WebCore.
-    static JWebCoreJavaBridge sJavaBridge;
-
-    private static class ConfigCallback implements ComponentCallbacks {
-        private final ArrayList<WeakReference<Handler>> mHandlers =
-                new ArrayList<WeakReference<Handler>>();
-        private final WindowManager mWindowManager;
-
-        ConfigCallback(WindowManager wm) {
-            mWindowManager = wm;
-        }
-
-        public synchronized void addHandler(Handler h) {
-            // No need to ever remove a Handler. If the BrowserFrame is
-            // destroyed, it will be collected and the WeakReference set to
-            // null. If it happens to still be around during a configuration
-            // change, the message will be ignored.
-            mHandlers.add(new WeakReference<Handler>(h));
-        }
-
-        public void onConfigurationChanged(Configuration newConfig) {
-            if (mHandlers.size() == 0) {
-                return;
-            }
-            int orientation =
-                    mWindowManager.getDefaultDisplay().getOrientation();
-            switch (orientation) {
-                case Surface.ROTATION_90:
-                    orientation = 90;
-                    break;
-                case Surface.ROTATION_180:
-                    orientation = 180;
-                    break;
-                case Surface.ROTATION_270:
-                    orientation = -90;
-                    break;
-                case Surface.ROTATION_0:
-                    orientation = 0;
-                    break;
-                default:
-                    break;
-            }
-            synchronized (this) {
-                // Create a list of handlers to remove. Go ahead and make it
-                // the same size to avoid resizing.
-                ArrayList<WeakReference> handlersToRemove =
-                        new ArrayList<WeakReference>(mHandlers.size());
-                for (WeakReference<Handler> wh : mHandlers) {
-                    Handler h = wh.get();
-                    if (h != null) {
-                        h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
-                                    orientation, 0));
-                    } else {
-                        handlersToRemove.add(wh);
-                    }
-                }
-                // Now remove all the null references.
-                for (WeakReference weak : handlersToRemove) {
-                    mHandlers.remove(weak);
-                }
-            }
-        }
-
-        public void onLowMemory() {}
-    }
-    static ConfigCallback sConfigCallback;
-
-    /**
-     * Create a new BrowserFrame to be used in an application.
-     * @param context An application context to use when retrieving assets.
-     * @param w A WebViewCore used as the view for this frame.
-     * @param proxy A CallbackProxy for posting messages to the UI thread and
-     *              querying a client for information.
-     * @param settings A WebSettings object that holds all settings.
-     * XXX: Called by WebCore thread.
-     */
-    public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
-            WebSettingsClassic settings, Map<String, Object> javascriptInterfaces) {
-
-        Context appContext = context.getApplicationContext();
-
-        // Create a global JWebCoreJavaBridge to handle timers and
-        // cookies in the WebCore thread.
-        if (sJavaBridge == null) {
-            sJavaBridge = new JWebCoreJavaBridge();
-            // set WebCore native cache size
-            ActivityManager am = (ActivityManager) context
-                    .getSystemService(Context.ACTIVITY_SERVICE);
-            if (am.getMemoryClass() > 16) {
-                sJavaBridge.setCacheSize(8 * 1024 * 1024);
-            } else {
-                sJavaBridge.setCacheSize(4 * 1024 * 1024);
-            }
-            // create CookieSyncManager with current Context
-            CookieSyncManager.createInstance(appContext);
-            // create PluginManager with current Context
-            PluginManager.getInstance(appContext);
-        }
-
-        if (sConfigCallback == null) {
-            sConfigCallback = new ConfigCallback(
-                    (WindowManager) appContext.getSystemService(
-                            Context.WINDOW_SERVICE));
-            ViewRootImpl.addConfigCallback(sConfigCallback);
-        }
-        sConfigCallback.addHandler(this);
-
-        mJavaScriptObjects = new HashMap<String, JSObject>();
-        addJavaScriptObjects(javascriptInterfaces);
-        mRemovedJavaScriptObjects = new HashSet<Object>();
-
-        mSettings = settings;
-        mContext = context;
-        mCallbackProxy = proxy;
-        mDatabase = WebViewDatabaseClassic.getInstance(appContext);
-        mWebViewCore = w;
-
-        AssetManager am = context.getAssets();
-        nativeCreateFrame(w, am, proxy.getBackForwardList());
-
-        if (DebugFlags.BROWSER_FRAME) {
-            Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
-        }
-    }
-
-    /**
-     * Load a url from the network or the filesystem into the main frame.
-     * Following the same behaviour as Safari, javascript: URLs are not passed
-     * to the main frame, instead they are evaluated immediately.
-     * @param url The url to load.
-     * @param extraHeaders The extra headers sent with this url. This should not
-     *            include the common headers like "user-agent". If it does, it
-     *            will be replaced by the intrinsic value of the WebView.
-     */
-    public void loadUrl(String url, Map<String, String> extraHeaders) {
-        mLoadInitFromJava = true;
-        if (URLUtil.isJavaScriptUrl(url)) {
-            // strip off the scheme and evaluate the string
-            stringByEvaluatingJavaScriptFromString(
-                    url.substring("javascript:".length()));
-        } else {
-            nativeLoadUrl(url, extraHeaders);
-        }
-        mLoadInitFromJava = false;
-    }
-
-    /**
-     * Load a url with "POST" method from the network into the main frame.
-     * @param url The url to load.
-     * @param data The data for POST request.
-     */
-    public void postUrl(String url, byte[] data) {
-        mLoadInitFromJava = true;
-        nativePostUrl(url, data);
-        mLoadInitFromJava = false;
-    }
-
-    /**
-     * Load the content as if it was loaded by the provided base URL. The
-     * historyUrl is used as the history entry for the load data.
-     * 
-     * @param baseUrl Base URL used to resolve relative paths in the content
-     * @param data Content to render in the browser
-     * @param mimeType Mimetype of the data being passed in
-     * @param encoding Character set encoding of the provided data.
-     * @param historyUrl URL to use as the history entry.
-     */
-    public void loadData(String baseUrl, String data, String mimeType,
-            String encoding, String historyUrl) {
-        mLoadInitFromJava = true;
-        if (historyUrl == null || historyUrl.length() == 0) {
-            historyUrl = "about:blank";
-        }
-        if (data == null) {
-            data = "";
-        }
-        
-        // Setup defaults for missing values. These defaults where taken from
-        // WebKit's WebFrame.mm
-        if (baseUrl == null || baseUrl.length() == 0) {
-            baseUrl = "about:blank";
-        }
-        if (mimeType == null || mimeType.length() == 0) {
-            mimeType = "text/html";
-        }
-        nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
-        mLoadInitFromJava = false;
-    }
-
-    /**
-     * Saves the contents of the frame as a web archive.
-     *
-     * @param basename The filename where the archive should be placed.
-     * @param autoname If false, takes filename to be a file. If true, filename
-     *                 is assumed to be a directory in which a filename will be
-     *                 chosen according to the url of the current page.
-     */
-    /* package */ String saveWebArchive(String basename, boolean autoname) {
-        return nativeSaveWebArchive(basename, autoname);
-    }
-
-    /**
-     * Go back or forward the number of steps given.
-     * @param steps A negative or positive number indicating the direction
-     *              and number of steps to move.
-     */
-    public void goBackOrForward(int steps) {
-        mLoadInitFromJava = true;
-        nativeGoBackOrForward(steps);
-        mLoadInitFromJava = false;
-    }
-
-    /**
-     * native callback
-     * Report an error to an activity.
-     * @param errorCode The HTTP error code.
-     * @param description Optional human-readable description. If no description
-     *     is given, we'll use a standard localized error message.
-     * @param failingUrl The URL that was being loaded when the error occurred.
-     * TODO: Report all errors including resource errors but include some kind
-     * of domain identifier. Change errorCode to an enum for a cleaner
-     * interface.
-     */
-    private void reportError(int errorCode, String description, String failingUrl) {
-        // As this is called for the main resource and loading will be stopped
-        // after, reset the state variables.
-        resetLoadingStates();
-        if (description == null || description.isEmpty()) {
-            description = ErrorStrings.getString(errorCode, mContext);
-        }
-        mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
-    }
-
-    private void resetLoadingStates() {
-        mCommitted = true;
-        mFirstLayoutDone = true;
-    }
-
-    /* package */boolean committed() {
-        return mCommitted;
-    }
-
-    /* package */boolean firstLayoutDone() {
-        return mFirstLayoutDone;
-    }
-
-    /* package */int loadType() {
-        return mLoadType;
-    }
-
-    /* package */void didFirstLayout() {
-        if (!mFirstLayoutDone) {
-            mFirstLayoutDone = true;
-            // ensure {@link WebViewCore#webkitDraw} is called as we were
-            // blocking the update in {@link #loadStarted}
-            mWebViewCore.contentDraw();
-        }
-    }
-
-    /**
-     * native callback
-     * Indicates the beginning of a new load.
-     * This method will be called once for the main frame.
-     */
-    private void loadStarted(String url, Bitmap favicon, int loadType,
-            boolean isMainFrame) {
-        mIsMainFrame = isMainFrame;
-
-        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
-            mLoadType = loadType;
-
-            if (isMainFrame) {
-                // Call onPageStarted for main frames.
-                mCallbackProxy.onPageStarted(url, favicon);
-                // as didFirstLayout() is only called for the main frame, reset 
-                // mFirstLayoutDone only for the main frames
-                mFirstLayoutDone = false;
-                mCommitted = false;
-                // remove pending draw to block update until mFirstLayoutDone is
-                // set to true in didFirstLayout()
-                mWebViewCore.clearContent();
-                mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
-            }
-        }
-    }
-
-    @SuppressWarnings("unused")
-    private void saveFormData(HashMap<String, String> data) {
-        if (mSettings.getSaveFormData()) {
-            final WebHistoryItem h = mCallbackProxy.getBackForwardList()
-                    .getCurrentItem();
-            if (h != null) {
-                String url = WebTextView.urlForAutoCompleteData(h.getUrl());
-                if (url != null) {
-                    mDatabase.setFormData(url, data);
-                }
-            }
-        }
-    }
-
-    @SuppressWarnings("unused")
-    private boolean shouldSaveFormData() {
-        if (mSettings.getSaveFormData()) {
-            final WebHistoryItem h = mCallbackProxy.getBackForwardList()
-                    .getCurrentItem();
-            return h != null && h.getUrl() != null;
-        }
-        return false;
-    }
-
-    /**
-     * native callback
-     * Indicates the WebKit has committed to the new load
-     */
-    private void transitionToCommitted(int loadType, boolean isMainFrame) {
-        // loadType is not used yet
-        if (isMainFrame) {
-            mCommitted = true;
-            mWebViewCore.getWebViewClassic().mViewManager.postResetStateAll();
-        }
-    }
-
-    /**
-     * native callback
-     * <p>
-     * Indicates the end of a new load.
-     * This method will be called once for the main frame.
-     */
-    private void loadFinished(String url, int loadType, boolean isMainFrame) {
-        // mIsMainFrame and isMainFrame are better be equal!!!
-
-        if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
-            if (isMainFrame) {
-                resetLoadingStates();
-                mCallbackProxy.switchOutDrawHistory();
-                mCallbackProxy.onPageFinished(url);
-            }
-        }
-    }
-
-    /**
-     * Destroy all native components of the BrowserFrame.
-     */
-    public void destroy() {
-        nativeDestroyFrame();
-        mBlockMessages = true;
-        removeCallbacksAndMessages(null);
-    }
-
-    /**
-     * Handle messages posted to us.
-     * @param msg The message to handle.
-     */
-    @Override
-    public void handleMessage(Message msg) {
-        if (mBlockMessages) {
-            return;
-        }
-        switch (msg.what) {
-            case FRAME_COMPLETED: {
-                if (mSettings.getSavePassword() && hasPasswordField()) {
-                    WebHistoryItem item = mCallbackProxy.getBackForwardList()
-                            .getCurrentItem();
-                    if (item != null) {
-                        WebAddress uri = new WebAddress(item.getUrl());
-                        String schemePlusHost = uri.getScheme() + uri.getHost();
-                        String[] up =
-                                WebViewDatabaseClassic.getInstance(mContext)
-                                        .getUsernamePassword(schemePlusHost);
-                        if (up != null && up[0] != null) {
-                            setUsernamePassword(up[0], up[1]);
-                        }
-                    }
-                }
-                break;
-            }
-
-            case POLICY_FUNCTION: {
-                nativeCallPolicyFunction(msg.arg1, msg.arg2);
-                break;
-            }
-
-            case ORIENTATION_CHANGED: {
-                if (mOrientation != msg.arg1) {
-                    mOrientation = msg.arg1;
-                    nativeOrientationChanged(msg.arg1);
-                }
-                break;
-            }
-
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Punch-through for WebCore to set the document
-     * title. Inform the Activity of the new title.
-     * @param title The new title of the document.
-     */
-    private void setTitle(String title) {
-        // FIXME: The activity must call getTitle (a native method) to get the
-        // title. We should try and cache the title if we can also keep it in
-        // sync with the document.
-        mCallbackProxy.onReceivedTitle(title);
-    }
-
-    /**
-     * Retrieves the render tree of this frame and puts it as the object for
-     * the message and sends the message.
-     * @param callback the message to use to send the render tree
-     */
-    public void externalRepresentation(Message callback) {
-        callback.obj = externalRepresentation();;
-        callback.sendToTarget();
-    }
-
-    /**
-     * Return the render tree as a string
-     */
-    private native String externalRepresentation();
-
-    /**
-     * Retrieves the visual text of the frames, puts it as the object for
-     * the message and sends the message.
-     * @param callback the message to use to send the visual text
-     */
-    public void documentAsText(Message callback) {
-        StringBuilder text = new StringBuilder();
-        if (callback.arg1 != 0) {
-            // Dump top frame as text.
-            text.append(documentAsText());
-        }
-        if (callback.arg2 != 0) {
-            // Dump child frames as text.
-            text.append(childFramesAsText());
-        }
-        callback.obj = text.toString();
-        callback.sendToTarget();
-    }
-
-    /**
-     * Return the text drawn on the screen as a string
-     */
-    private native String documentAsText();
-
-    /**
-     * Return the text drawn on the child frames as a string
-     */
-    private native String childFramesAsText();
-
-    /*
-     * This method is called by WebCore to inform the frame that
-     * the Javascript window object has been cleared.
-     * We should re-attach any attached js interfaces.
-     */
-    private void windowObjectCleared(int nativeFramePointer) {
-        Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
-        while (iter.hasNext())  {
-            String interfaceName = iter.next();
-            JSObject jsobject = mJavaScriptObjects.get(interfaceName);
-            if (jsobject != null && jsobject.object != null) {
-                nativeAddJavascriptInterface(nativeFramePointer,
-                        jsobject.object, interfaceName, jsobject.requireAnnotation);
-            }
-        }
-        mRemovedJavaScriptObjects.clear();
-    }
-
-    /*
-     * Add javascript objects to the internal list of objects. The default behavior
-     * is to allow access to inherited methods (no annotation needed). This is only
-     * used when js objects are passed through a constructor (via a hidden constructor).
-     *
-     * @TODO change the default behavior to be compatible with the public addjavascriptinterface
-     */
-    private void addJavaScriptObjects(Map<String, Object> javascriptInterfaces) {
-
-        // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above.
-        if (javascriptInterfaces == null) return;
-        Iterator<String> iter = javascriptInterfaces.keySet().iterator();
-        while (iter.hasNext())  {
-            String interfaceName = iter.next();
-            Object object = javascriptInterfaces.get(interfaceName);
-            if (object != null) {
-                mJavaScriptObjects.put(interfaceName, new JSObject(object, false));
-            }
-        }
-    }
-
-    /**
-     * This method is called by WebCore to check whether application
-     * wants to hijack url loading
-     */
-    public boolean handleUrl(String url) {
-        if (mLoadInitFromJava == true) {
-            return false;
-        }
-        if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
-            // if the url is hijacked, reset the state of the BrowserFrame
-            didFirstLayout();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public void addJavascriptInterface(Object obj, String interfaceName,
-            boolean requireAnnotation) {
-        assert obj != null;
-        removeJavascriptInterface(interfaceName);
-        mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation));
-    }
-
-    public void removeJavascriptInterface(String interfaceName) {
-        // We keep a reference to the removed object because the native side holds only a weak
-        // reference and we need to allow the object to continue to be used until the page has been
-        // navigated.
-        if (mJavaScriptObjects.containsKey(interfaceName)) {
-            mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName));
-        }
-    }
-
-    /**
-     * Called by JNI.  Given a URI, find the associated file and return its size
-     * @param uri A String representing the URI of the desired file.
-     * @return int The size of the given file.
-     */
-    private int getFileSize(String uri) {
-        int size = 0;
-        try {
-            InputStream stream = mContext.getContentResolver()
-                            .openInputStream(Uri.parse(uri));
-            size = stream.available();
-            stream.close();
-        } catch (Exception e) {}
-        return size;
-    }
-
-    /**
-     * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
-     * copy the resource into buffer.
-     * @param uri A String representing the URI of the desired file.
-     * @param buffer The byte array to copy the data into.
-     * @param offset The offet into buffer to place the data.
-     * @param expectedSize The size that the buffer has allocated for this file.
-     * @return int The size of the given file, or zero if it fails.
-     */
-    private int getFile(String uri, byte[] buffer, int offset,
-            int expectedSize) {
-        int size = 0;
-        try {
-            InputStream stream = mContext.getContentResolver()
-                            .openInputStream(Uri.parse(uri));
-            size = stream.available();
-            if (size <= expectedSize && buffer != null
-                    && buffer.length - offset >= size) {
-                stream.read(buffer, offset, size);
-            } else {
-                size = 0;
-            }
-            stream.close();
-        } catch (java.io.FileNotFoundException e) {
-            Log.e(LOGTAG, "FileNotFoundException:" + e);
-            size = 0;
-        } catch (java.io.IOException e2) {
-            Log.e(LOGTAG, "IOException: " + e2);
-            size = 0;
-        }
-        return size;
-    }
-
-    /**
-     * Get the InputStream for an Android resource
-     * There are three different kinds of android resources:
-     * - file:///android_res
-     * - file:///android_asset
-     * - content://
-     * @param url The url to load.
-     * @return An InputStream to the android resource
-     */
-    private InputStream inputStreamForAndroidResource(String url) {
-        final String ANDROID_ASSET = URLUtil.ASSET_BASE;
-        final String ANDROID_RESOURCE = URLUtil.RESOURCE_BASE;
-        final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
-
-        if (url.startsWith(ANDROID_RESOURCE)) {
-            url = url.replaceFirst(ANDROID_RESOURCE, "");
-            if (url == null || url.length() == 0) {
-                Log.e(LOGTAG, "url has length 0 " + url);
-                return null;
-            }
-            int slash = url.indexOf('/');
-            int dot = url.indexOf('.', slash);
-            if (slash == -1 || dot == -1) {
-                Log.e(LOGTAG, "Incorrect res path: " + url);
-                return null;
-            }
-            String subClassName = url.substring(0, slash);
-            String fieldName = url.substring(slash + 1, dot);
-            String errorMsg = null;
-            try {
-                final Class<?> d = mContext.getApplicationContext()
-                        .getClassLoader().loadClass(
-                                mContext.getPackageName() + ".R$"
-                                        + subClassName);
-                final java.lang.reflect.Field field = d.getField(fieldName);
-                final int id = field.getInt(null);
-                TypedValue value = new TypedValue();
-                mContext.getResources().getValue(id, value, true);
-                if (value.type == TypedValue.TYPE_STRING) {
-                    return mContext.getAssets().openNonAsset(
-                            value.assetCookie, value.string.toString(),
-                            AssetManager.ACCESS_STREAMING);
-                } else {
-                    // Old stack only supports TYPE_STRING for res files
-                    Log.e(LOGTAG, "not of type string: " + url);
-                    return null;
-                }
-            } catch (Exception e) {
-                Log.e(LOGTAG, "Exception: " + url);
-                return null;
-            }
-        } else if (url.startsWith(ANDROID_ASSET)) {
-            String assetUrl = url.replaceFirst(ANDROID_ASSET, "");
-            try {
-                AssetManager assets = mContext.getAssets();
-                Uri uri = Uri.parse(assetUrl);
-                return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
-            } catch (IOException e) {
-                return null;
-            } catch (Exception e) {
-                Log.w(LOGTAG, "Problem loading url: " + url, e);
-                return null;
-            }
-        } else if (mSettings.getAllowContentAccess() &&
-                   url.startsWith(ANDROID_CONTENT)) {
-            try {
-                // Strip off MIME type. If we don't do this, we can fail to
-                // load Gmail attachments, because the URL being loaded doesn't
-                // exactly match the URL we have permission to read.
-                int mimeIndex = url.lastIndexOf('?');
-                if (mimeIndex != -1) {
-                    url = url.substring(0, mimeIndex);
-                }
-                Uri uri = Uri.parse(url);
-                return mContext.getContentResolver().openInputStream(uri);
-            } catch (Exception e) {
-                Log.e(LOGTAG, "Exception: " + url);
-                return null;
-            }
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * If this looks like a POST request (form submission) containing a username
-     * and password, give the user the option of saving them. Will either do
-     * nothing, or block until the UI interaction is complete.
-     *
-     * Called directly by WebKit.
-     *
-     * @param postData The data about to be sent as the body of a POST request.
-     * @param username The username entered by the user (sniffed from the DOM).
-     * @param password The password entered by the user (sniffed from the DOM).
-     */
-    private void maybeSavePassword(
-            byte[] postData, String username, String password) {
-        if (postData == null
-                || username == null || username.isEmpty()
-                || password == null || password.isEmpty()) {
-            return; // No password to save.
-        }
-
-        if (!mSettings.getSavePassword()) {
-            return; // User doesn't want to save passwords.
-        }
-
-        try {
-            if (DebugFlags.BROWSER_FRAME) {
-                Assert.assertNotNull(mCallbackProxy.getBackForwardList()
-                        .getCurrentItem());
-            }
-            WebAddress uri = new WebAddress(mCallbackProxy
-                    .getBackForwardList().getCurrentItem().getUrl());
-            String schemePlusHost = uri.getScheme() + uri.getHost();
-            // Check to see if the username & password appear in
-            // the post data (there could be another form on the
-            // page and that was posted instead.
-            String postString = new String(postData);
-            if (postString.contains(URLEncoder.encode(username)) &&
-                    postString.contains(URLEncoder.encode(password))) {
-                String[] saved = mDatabase.getUsernamePassword(
-                        schemePlusHost);
-                if (saved != null) {
-                    // null username implies that user has chosen not to
-                    // save password
-                    if (saved[0] != null) {
-                        // non-null username implies that user has
-                        // chosen to save password, so update the
-                        // recorded password
-                        mDatabase.setUsernamePassword(schemePlusHost, username, password);
-                    }
-                } else {
-                    // CallbackProxy will handle creating the resume
-                    // message
-                    mCallbackProxy.onSavePassword(schemePlusHost, username,
-                            password, null);
-                }
-            }
-        } catch (ParseException ex) {
-            // if it is bad uri, don't save its password
-        }
-    }
-
-    // Called by jni from the chrome network stack.
-    private WebResourceResponse shouldInterceptRequest(String url) {
-        InputStream androidResource = inputStreamForAndroidResource(url);
-        if (androidResource != null) {
-            return new WebResourceResponse(null, null, androidResource);
-        }
-
-        // Note that we check this after looking for an android_asset or
-        // android_res URL, as we allow those even if file access is disabled.
-        if (!mSettings.getAllowFileAccess() && url.startsWith("file://")) {
-            return new WebResourceResponse(null, null, null);
-        }
-
-        WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url);
-        if (response == null && "browser:incognito".equals(url)) {
-            try {
-                Resources res = mContext.getResources();
-                InputStream ins = res.openRawResource(
-                        com.android.internal.R.raw.incognito_mode_start_page);
-                response = new WebResourceResponse("text/html", "utf8", ins);
-            } catch (NotFoundException ex) {
-                // This shouldn't happen, but try and gracefully handle it jic
-                Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex);
-            }
-        }
-        return response;
-    }
-
-    /**
-     * Set the progress for the browser activity.  Called by native code.
-     * Uses a delay so it does not happen too often.
-     * @param newProgress An int between zero and one hundred representing
-     *                    the current progress percentage of loading the page.
-     */
-    private void setProgress(int newProgress) {
-        mCallbackProxy.onProgressChanged(newProgress);
-        if (newProgress == 100) {
-            sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
-        }
-        // FIXME: Need to figure out a better way to switch out of the history
-        // drawing mode. Maybe we can somehow compare the history picture with 
-        // the current picture, and switch when it contains more content.
-        if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
-            mCallbackProxy.switchOutDrawHistory();
-        }
-    }
-
-    /**
-     * Send the icon to the activity for display.
-     * @param icon A Bitmap representing a page's favicon.
-     */
-    private void didReceiveIcon(Bitmap icon) {
-        mCallbackProxy.onReceivedIcon(icon);
-    }
-
-    // Called by JNI when an apple-touch-icon attribute was found.
-    private void didReceiveTouchIconUrl(String url, boolean precomposed) {
-        mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
-    }
-
-    /**
-     * Request a new window from the client.
-     * @return The BrowserFrame object stored in the new WebView.
-     */
-    private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
-        return mCallbackProxy.createWindow(dialog, userGesture);
-    }
-
-    /**
-     * Try to focus this WebView.
-     */
-    private void requestFocus() {
-        mCallbackProxy.onRequestFocus();
-    }
-
-    /**
-     * Close this frame and window.
-     */
-    private void closeWindow(WebViewCore w) {
-        mCallbackProxy.onCloseWindow(w.getWebViewClassic());
-    }
-
-    // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
-    static final int POLICY_USE = 0;
-    static final int POLICY_IGNORE = 2;
-
-    private void decidePolicyForFormResubmission(int policyFunction) {
-        Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
-                POLICY_IGNORE);
-        Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
-                POLICY_USE);
-        mCallbackProxy.onFormResubmission(dontResend, resend);
-    }
-
-    /**
-     * Tell the activity to update its global history.
-     */
-    private void updateVisitedHistory(String url, boolean isReload) {
-        mCallbackProxy.doUpdateVisitedHistory(url, isReload);
-    }
-
-    /**
-     * Get the CallbackProxy for sending messages to the UI thread.
-     */
-    /* package */ CallbackProxy getCallbackProxy() {
-        return mCallbackProxy;
-    }
-
-    /**
-     * Returns the User Agent used by this frame
-     */
-    String getUserAgentString() {
-        return mSettings.getUserAgentString();
-    }
-
-    // These ids need to be in sync with enum rawResId in PlatformBridge.h
-    private static final int NODOMAIN = 1;
-    private static final int LOADERROR = 2;
-    /* package */ static final int DRAWABLEDIR = 3;
-    private static final int FILE_UPLOAD_LABEL = 4;
-    private static final int RESET_LABEL = 5;
-    private static final int SUBMIT_LABEL = 6;
-    private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
-
-    private String getRawResFilename(int id) {
-        return getRawResFilename(id, mContext);
-    }
-    /* package */ static String getRawResFilename(int id, Context context) {
-        int resid;
-        switch (id) {
-            case NODOMAIN:
-                resid = com.android.internal.R.raw.nodomain;
-                break;
-
-            case LOADERROR:
-                resid = com.android.internal.R.raw.loaderror;
-                break;
-
-            case DRAWABLEDIR:
-                // use one known resource to find the drawable directory
-                resid = com.android.internal.R.drawable.btn_check_off;
-                break;
-
-            case FILE_UPLOAD_LABEL:
-                return context.getResources().getString(
-                        com.android.internal.R.string.upload_file);
-
-            case RESET_LABEL:
-                return context.getResources().getString(
-                        com.android.internal.R.string.reset);
-
-            case SUBMIT_LABEL:
-                return context.getResources().getString(
-                        com.android.internal.R.string.submit);
-
-            case FILE_UPLOAD_NO_FILE_CHOSEN:
-                return context.getResources().getString(
-                        com.android.internal.R.string.no_file_chosen);
-
-            default:
-                Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
-                return "";
-        }
-        TypedValue value = new TypedValue();
-        context.getResources().getValue(resid, value, true);
-        if (id == DRAWABLEDIR) {
-            String path = value.string.toString();
-            int index = path.lastIndexOf('/');
-            if (index < 0) {
-                Log.e(LOGTAG, "Can't find drawable directory.");
-                return "";
-            }
-            return path.substring(0, index + 1);
-        }
-        return value.string.toString();
-    }
-
-    private float density() {
-        return WebViewCore.getFixedDisplayDensity(mContext);
-    }
-
-    /**
-     * Called by JNI when the native HTTP stack gets an authentication request.
-     *
-     * We delegate the request to CallbackProxy, and route its response to
-     * {@link #nativeAuthenticationProceed(int, String, String)} or
-     * {@link #nativeAuthenticationCancel(int)}.
-     *
-     * We don't care what thread the callback is invoked on. All threading is
-     * handled on the C++ side, because the WebKit thread may be blocked on a
-     * synchronous call and unable to pump our MessageQueue.
-     */
-    private void didReceiveAuthenticationChallenge(
-            final int handle, String host, String realm, final boolean useCachedCredentials,
-            final boolean suppressDialog) {
-
-        HttpAuthHandler handler = new HttpAuthHandler() {
-
-            @Override
-            public boolean useHttpAuthUsernamePassword() {
-                return useCachedCredentials;
-            }
-
-            @Override
-            public void proceed(String username, String password) {
-                nativeAuthenticationProceed(handle, username, password);
-            }
-
-            @Override
-            public void cancel() {
-                nativeAuthenticationCancel(handle);
-            }
-
-            @Override
-            public boolean suppressDialog() {
-                return suppressDialog;
-            }
-        };
-        mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
-    }
-
-    /**
-     * Called by JNI when the Chromium HTTP stack gets an invalid certificate chain.
-     *
-     * We delegate the request to CallbackProxy, and route its response to
-     * {@link #nativeSslCertErrorProceed(int)} or
-     * {@link #nativeSslCertErrorCancel(int, int)}.
-     */
-    private void reportSslCertError(final int handle, final int certError, byte certDER[],
-            String url) {
-        final SslError sslError;
-        try {
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate) cf.generateCertificate(
-                    new ByteArrayInputStream(certDER));
-            SslCertificate sslCert = new SslCertificate(cert);
-            sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url);
-        } catch (Exception e) {
-            // Can't get the certificate, not much to do.
-            Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
-            nativeSslCertErrorCancel(handle, certError);
-            return;
-        }
-
-        if (SslCertLookupTable.getInstance().isAllowed(sslError)) {
-            nativeSslCertErrorProceed(handle);
-            mCallbackProxy.onProceededAfterSslError(sslError);
-            return;
-        }
-
-        SslErrorHandler handler = new SslErrorHandler() {
-            @Override
-            public void proceed() {
-                SslCertLookupTable.getInstance().setIsAllowed(sslError);
-                post(new Runnable() {
-                        public void run() {
-                            nativeSslCertErrorProceed(handle);
-                        }
-                    });
-            }
-            @Override
-            public void cancel() {
-                post(new Runnable() {
-                        public void run() {
-                            nativeSslCertErrorCancel(handle, certError);
-                        }
-                    });
-            }
-        };
-        mCallbackProxy.onReceivedSslError(handler, sslError);
-    }
-
-    /**
-     * Called by JNI when the native HTTPS stack gets a client
-     * certificate request.
-     *
-     * We delegate the request to CallbackProxy, and route its response to
-     * {@link #nativeSslClientCert(int, X509Certificate)}.
-     */
-    private void requestClientCert(int handle, String hostAndPort) {
-        SslClientCertLookupTable table = SslClientCertLookupTable.getInstance();
-        if (table.IsAllowed(hostAndPort)) {
-            // previously allowed
-            PrivateKey pkey = table.PrivateKey(hostAndPort);
-            if (pkey instanceof OpenSSLKeyHolder) {
-                OpenSSLKey sslKey = ((OpenSSLKeyHolder) pkey).getOpenSSLKey();
-                nativeSslClientCert(handle,
-                                    sslKey.getPkeyContext(),
-                                    table.CertificateChain(hostAndPort));
-            } else {
-                nativeSslClientCert(handle,
-                                    pkey.getEncoded(),
-                                    table.CertificateChain(hostAndPort));
-            }
-        } else if (table.IsDenied(hostAndPort)) {
-            // previously denied
-            nativeSslClientCert(handle, 0, null);
-        } else {
-            // previously ignored or new
-            mCallbackProxy.onReceivedClientCertRequest(
-                    new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort);
-        }
-    }
-
-    /**
-     * Called by JNI when the native HTTP stack needs to download a file.
-     *
-     * We delegate the request to CallbackProxy, which owns the current app's
-     * DownloadListener.
-     */
-    private void downloadStart(String url, String userAgent,
-            String contentDisposition, String mimeType, String referer, long contentLength) {
-        // This will only work if the url ends with the filename
-        if (mimeType.isEmpty()) {
-            try {
-                String extension = url.substring(url.lastIndexOf('.') + 1);
-                mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension);
-                // MimeUtils might return null, not sure if downloadmanager is happy with that
-                if (mimeType == null)
-                    mimeType = "";
-            } catch(IndexOutOfBoundsException exception) {
-                // mimeType string end with a '.', not much to do
-            }
-        }
-        mimeType = MimeTypeMap.getSingleton().remapGenericMimeType(
-                mimeType, url, contentDisposition);
-
-        if (CertTool.getCertType(mimeType) != null) {
-            mKeyStoreHandler = new KeyStoreHandler(mimeType);
-        } else {
-            mCallbackProxy.onDownloadStart(url, userAgent,
-                contentDisposition, mimeType, referer, contentLength);
-        }
-    }
-
-    /**
-     * Called by JNI for Chrome HTTP stack when the Java side needs to access the data.
-     */
-    private void didReceiveData(byte data[], int size) {
-        if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size);
-    }
-
-    private void didFinishLoading() {
-      if (mKeyStoreHandler != null) {
-          mKeyStoreHandler.installCert(mContext);
-          mKeyStoreHandler = null;
-      }
-    }
-
-    /**
-     * Called by JNI when we recieve a certificate for the page's main resource.
-     * Used by the Chromium HTTP stack only.
-     */
-    private void setCertificate(byte cert_der[]) {
-        try {
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert = (X509Certificate) cf.generateCertificate(
-                    new ByteArrayInputStream(cert_der));
-            mCallbackProxy.onReceivedCertificate(new SslCertificate(cert));
-        } catch (Exception e) {
-            // Can't get the certificate, not much to do.
-            Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
-            return;
-        }
-    }
-
-    /**
-     * Called by JNI when processing the X-Auto-Login header.
-     */
-    private void autoLogin(String realm, String account, String args) {
-        mCallbackProxy.onReceivedLoginRequest(realm, account, args);
-    }
-
-    //==========================================================================
-    // native functions
-    //==========================================================================
-
-    /**
-     * Create a new native frame for a given WebView
-     * @param w     A WebView that the frame draws into.
-     * @param am    AssetManager to use to get assets.
-     * @param list  The native side will add and remove items from this list as
-     *              the native list changes.
-     */
-    private native void nativeCreateFrame(WebViewCore w, AssetManager am,
-            WebBackForwardList list);
-
-    /**
-     * Destroy the native frame.
-     */
-    public native void nativeDestroyFrame();
-
-    private native void nativeCallPolicyFunction(int policyFunction,
-            int decision);
-
-    /**
-     * Reload the current main frame.
-     */
-    public native void reload(boolean allowStale);
-
-    /**
-     * Go back or forward the number of steps given.
-     * @param steps A negative or positive number indicating the direction
-     *              and number of steps to move.
-     */
-    private native void nativeGoBackOrForward(int steps);
-
-    /**
-     * stringByEvaluatingJavaScriptFromString will execute the
-     * JS passed in in the context of this browser frame.
-     * @param script A javascript string to execute
-     * 
-     * @return string result of execution or null
-     */
-    public native String stringByEvaluatingJavaScriptFromString(String script);
-
-    /**
-     * Add a javascript interface to the main frame.
-     */
-    private native void nativeAddJavascriptInterface(int nativeFramePointer,
-            Object obj, String interfaceName, boolean requireAnnotation);
-
-    public native void clearCache();
-
-    /**
-     * Returns false if the url is bad.
-     */
-    private native void nativeLoadUrl(String url, Map<String, String> headers);
-
-    private native void nativePostUrl(String url, byte[] postData);
-
-    private native void nativeLoadData(String baseUrl, String data,
-            String mimeType, String encoding, String historyUrl);
-
-    /**
-     * Stop loading the current page.
-     */
-    public void stopLoading() {
-        if (mIsMainFrame) {
-            resetLoadingStates();
-        }
-        nativeStopLoading();
-    }
-
-    private native void nativeStopLoading();
-
-    /**
-     * Return true if the document has images.
-     */
-    public native boolean documentHasImages();
-
-    /**
-     * @return TRUE if there is a password field in the current frame
-     */
-    private native boolean hasPasswordField();
-
-    /**
-     * Get username and password in the current frame. If found, String[0] is
-     * username and String[1] is password. Otherwise return NULL.
-     * @return String[]
-     */
-    private native String[] getUsernamePassword();
-
-    /**
-     * Set username and password to the proper fields in the current frame
-     * @param username
-     * @param password
-     */
-    private native void setUsernamePassword(String username, String password);
-
-    private native String nativeSaveWebArchive(String basename, boolean autoname);
-
-    private native void nativeOrientationChanged(int orientation);
-
-    private native void nativeAuthenticationProceed(int handle, String username, String password);
-    private native void nativeAuthenticationCancel(int handle);
-
-    private native void nativeSslCertErrorProceed(int handle);
-    private native void nativeSslCertErrorCancel(int handle, int certError);
-
-    native void nativeSslClientCert(int handle,
-                                    long ctx,
-                                    byte[][] asn1DerEncodedCertificateChain);
-
-    native void nativeSslClientCert(int handle,
-                                    byte[] pkey,
-                                    byte[][] asn1DerEncodedCertificateChain);
-
-    /**
-     * Returns true when the contents of the frame is an RTL or vertical-rl
-     * page. This is used for determining whether a frame should be initially
-     * scrolled right-most as opposed to left-most.
-     * @return true when the frame should be initially scrolled right-most
-     * based on the text direction and writing mode.
-     */
-    /* package */ boolean getShouldStartScrolledRight() {
-        return nativeGetShouldStartScrolledRight(mNativeFrame);
-    }
-
-    private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
-}
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
deleted file mode 100644
index 334526b..0000000
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.util.LinkedList;
-import java.util.ListIterator;
-
-/** Utility class optimized for accumulating bytes, and then spitting
-    them back out.  It does not optimize for returning the result in a
-    single array, though this is supported in the API. It is fastest
-    if the retrieval can be done via iterating through chunks.
-*/
-class ByteArrayBuilder {
-
-    private static final int DEFAULT_CAPACITY = 8192;
-
-    // Global pool of chunks to be used by other ByteArrayBuilders.
-    private static final LinkedList<SoftReference<Chunk>> sPool =
-            new LinkedList<SoftReference<Chunk>>();
-    // Reference queue for processing gc'd entries.
-    private static final ReferenceQueue<Chunk> sQueue =
-            new ReferenceQueue<Chunk>();
-
-    private LinkedList<Chunk> mChunks;
-
-    public ByteArrayBuilder() {
-        mChunks = new LinkedList<Chunk>();
-    }
-
-    public synchronized void append(byte[] array, int offset, int length) {
-        while (length > 0) {
-            Chunk c = null;
-            if (mChunks.isEmpty()) {
-                c = obtainChunk(length);
-                mChunks.addLast(c);
-            } else {
-                c = mChunks.getLast();
-                if (c.mLength == c.mArray.length) {
-                    c = obtainChunk(length);
-                    mChunks.addLast(c);
-                }
-            }
-            int amount = Math.min(length, c.mArray.length - c.mLength);
-            System.arraycopy(array, offset, c.mArray, c.mLength, amount);
-            c.mLength += amount;
-            length -= amount;
-            offset += amount;
-        }
-    }
-
-    /**
-     * The fastest way to retrieve the data is to iterate through the
-     * chunks.  This returns the first chunk.  Note: this pulls the
-     * chunk out of the queue.  The caller must call Chunk.release() to
-     * dispose of it.
-     */
-    public synchronized Chunk getFirstChunk() {
-        if (mChunks.isEmpty()) return null;
-        return mChunks.removeFirst();
-    }
-
-    public synchronized boolean isEmpty() {
-        return mChunks.isEmpty();
-    }
-
-    public synchronized int getByteSize() {
-        int total = 0;
-        ListIterator<Chunk> it = mChunks.listIterator(0);
-        while (it.hasNext()) {
-            Chunk c = it.next();
-            total += c.mLength;
-        }
-        return total;
-    }
-
-    public synchronized void clear() {
-        Chunk c = getFirstChunk();
-        while (c != null) {
-            c.release();
-            c = getFirstChunk();
-        }
-    }
-
-    // Must be called with lock held on sPool.
-    private void processPoolLocked() {
-        while (true) {
-            SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
-            if (entry == null) {
-                break;
-            }
-            sPool.remove(entry);
-        }
-    }
-
-    private Chunk obtainChunk(int length) {
-        // Correct a small length.
-        if (length < DEFAULT_CAPACITY) {
-            length = DEFAULT_CAPACITY;
-        }
-        synchronized (sPool) {
-            // Process any queued references and remove them from the pool.
-            processPoolLocked();
-            if (!sPool.isEmpty()) {
-                Chunk c = sPool.removeFirst().get();
-                // The first item may have been queued after processPoolLocked
-                // so check for null.
-                if (c != null) {
-                    return c;
-                }
-            }
-            return new Chunk(length);
-        }
-    }
-
-    public static class Chunk {
-        public byte[]  mArray;
-        public int     mLength;
-
-        public Chunk(int length) {
-            mArray = new byte[length];
-            mLength = 0;
-        }
-
-        /**
-         * Release the chunk and make it available for reuse.
-         */
-        public void release() {
-            mLength = 0;
-            synchronized (sPool) {
-                // Add the chunk back to the pool as a SoftReference so it can
-                // be gc'd if needed.
-                sPool.offer(new SoftReference<Chunk>(this, sQueue));
-                sPool.notifyAll();
-            }
-        }
-
-    }
-}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
deleted file mode 100644
index 7707392..0000000
--- a/core/java/android/webkit/CallbackProxy.java
+++ /dev/null
@@ -1,1452 +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.
- */
-
-package android.webkit;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Browser;
-import android.util.Log;
-import android.view.KeyEvent;
-import com.android.internal.R;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class is a proxy class for handling WebCore -> UI thread messaging. All
- * the callback functions are called from the WebCore thread and messages are
- * posted to the UI thread for the actual client callback.
- */
-/*
- * This class is created in the UI thread so its handler and any private classes
- * that extend Handler will operate in the UI thread.
- */
-class CallbackProxy extends Handler {
-    // Logging tag
-    static final String LOGTAG = "WebViewCallback";
-    // Enables API callback tracing
-    private static final boolean TRACE = DebugFlags.TRACE_CALLBACK;
-    // Instance of WebViewClient that is the client callback.
-    private volatile WebViewClient mWebViewClient;
-    // Instance of WebChromeClient for handling all chrome functions.
-    private volatile WebChromeClient mWebChromeClient;
-    // Instance of WebViewClassic for handling UI requests.
-    private final WebViewClassic mWebView;
-    // Client registered callback listener for download events
-    private volatile DownloadListener mDownloadListener;
-    // Keep track of multiple progress updates.
-    private boolean mProgressUpdatePending;
-    // Keep track of the last progress amount.
-    // Start with 100 to indicate it is not in load for the empty page.
-    private volatile int mLatestProgress = 100;
-    // Back/Forward list
-    private final WebBackForwardListClassic mBackForwardList;
-    // Back/Forward list client
-    private volatile WebBackForwardListClient mWebBackForwardListClient;
-    // Used to call startActivity during url override.
-    private final Context mContext;
-    // block messages flag for destroy
-    private boolean mBlockMessages;
-
-    // Message IDs
-    private static final int PAGE_STARTED                         = 100;
-    private static final int RECEIVED_ICON                        = 101;
-    private static final int RECEIVED_TITLE                       = 102;
-    private static final int OVERRIDE_URL                         = 103;
-    private static final int AUTH_REQUEST                         = 104;
-    private static final int SSL_ERROR                            = 105;
-    private static final int PROGRESS                             = 106;
-    private static final int UPDATE_VISITED                       = 107;
-    private static final int LOAD_RESOURCE                        = 108;
-    private static final int CREATE_WINDOW                        = 109;
-    private static final int CLOSE_WINDOW                         = 110;
-    private static final int SAVE_PASSWORD                        = 111;
-    private static final int JS_DIALOG                            = 112;
-    private static final int ASYNC_KEYEVENTS                      = 116;
-    private static final int DOWNLOAD_FILE                        = 118;
-    private static final int REPORT_ERROR                         = 119;
-    private static final int RESEND_POST_DATA                     = 120;
-    private static final int PAGE_FINISHED                        = 121;
-    private static final int REQUEST_FOCUS                        = 122;
-    private static final int SCALE_CHANGED                        = 123;
-    private static final int RECEIVED_CERTIFICATE                 = 124;
-    private static final int SWITCH_OUT_HISTORY                   = 125;
-    private static final int EXCEEDED_DATABASE_QUOTA              = 126;
-    private static final int REACHED_APPCACHE_MAXSIZE             = 127;
-    private static final int JS_TIMEOUT                           = 128;
-    private static final int ADD_MESSAGE_TO_CONSOLE               = 129;
-    private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT  = 130;
-    private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT  = 131;
-    private static final int RECEIVED_TOUCH_ICON_URL              = 132;
-    private static final int GET_VISITED_HISTORY                  = 133;
-    private static final int OPEN_FILE_CHOOSER                    = 134;
-    private static final int ADD_HISTORY_ITEM                     = 135;
-    private static final int HISTORY_INDEX_CHANGED                = 136;
-    private static final int AUTH_CREDENTIALS                     = 137;
-    private static final int AUTO_LOGIN                           = 140;
-    private static final int CLIENT_CERT_REQUEST                  = 141;
-    private static final int PROCEEDED_AFTER_SSL_ERROR            = 144;
-
-    // Message triggered by the client to resume execution
-    private static final int NOTIFY                               = 200;
-
-    // Result transportation object for returning results across thread
-    // boundaries.
-    private static class ResultTransport<E> {
-        // Private result object
-        private E mResult;
-
-        public ResultTransport(E defaultResult) {
-            mResult = defaultResult;
-        }
-
-        public synchronized void setResult(E result) {
-            mResult = result;
-        }
-
-        public synchronized E getResult() {
-            return mResult;
-        }
-    }
-
-    private class JsResultReceiver implements JsResult.ResultReceiver {
-        // This prevents a user from interacting with the result before WebCore is
-        // ready to handle it.
-        private boolean mReady;
-        // Tells us if the user tried to confirm or cancel the result before WebCore
-        // is ready.
-        private boolean mTriedToNotifyBeforeReady;
-
-        public JsPromptResult mJsResult = new JsPromptResult(this);
-
-        final void setReady() {
-            mReady = true;
-            if (mTriedToNotifyBeforeReady) {
-                notifyCallbackProxy();
-            }
-        }
-
-        /* Wake up the WebCore thread. */
-        @Override
-        public void onJsResultComplete(JsResult result) {
-            if (mReady) {
-                notifyCallbackProxy();
-            } else {
-                mTriedToNotifyBeforeReady = true;
-            }
-        }
-
-        private void notifyCallbackProxy() {
-            synchronized (CallbackProxy.this) {
-                CallbackProxy.this.notify();
-            }
-        }
-}
-
-    /**
-     * Construct a new CallbackProxy.
-     */
-    public CallbackProxy(Context context, WebViewClassic w) {
-        // Used to start a default activity.
-        mContext = context;
-        mWebView = w;
-        mBackForwardList = new WebBackForwardListClassic(this);
-    }
-
-    protected synchronized void blockMessages() {
-        mBlockMessages = true;
-    }
-
-    protected synchronized boolean messagesBlocked() {
-        return mBlockMessages;
-    }
-
-    protected void shutdown() {
-        removeCallbacksAndMessages(null);
-        setWebViewClient(null);
-        setWebChromeClient(null);
-    }
-
-    /**
-     * Set the WebViewClient.
-     * @param client An implementation of WebViewClient.
-     */
-    public void setWebViewClient(WebViewClient client) {
-        mWebViewClient = client;
-    }
-
-    /**
-     * Get the WebViewClient.
-     * @return the current WebViewClient instance.
-     */
-    public WebViewClient getWebViewClient() {
-       return mWebViewClient;
-    }
-
-    /**
-     * Set the WebChromeClient.
-     * @param client An implementation of WebChromeClient.
-     */
-    public void setWebChromeClient(WebChromeClient client) {
-        mWebChromeClient = client;
-    }
-
-    /**
-     * Get the WebChromeClient.
-     * @return the current WebChromeClient instance.
-     */
-    public WebChromeClient getWebChromeClient() {
-       return mWebChromeClient;
-    }
-
-    /**
-     * Set the client DownloadListener.
-     * @param client An implementation of DownloadListener.
-     */
-    public void setDownloadListener(DownloadListener client) {
-        mDownloadListener = client;
-    }
-
-    /**
-     * Get the Back/Forward list to return to the user or to update the cached
-     * history list.
-     */
-    public WebBackForwardListClassic getBackForwardList() {
-        return mBackForwardList;
-    }
-
-    void setWebBackForwardListClient(WebBackForwardListClient client) {
-        mWebBackForwardListClient = client;
-    }
-
-    WebBackForwardListClient getWebBackForwardListClient() {
-        return mWebBackForwardListClient;
-    }
-
-    /**
-     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
-     * side will post a message to call this method.
-     */
-    public boolean uiOverrideUrlLoading(String overrideUrl) {
-        if (overrideUrl == null || overrideUrl.length() == 0) {
-            return false;
-        }
-        boolean override = false;
-        if (mWebViewClient != null) {
-            if (TRACE) Log.d(LOGTAG, "shouldOverrideUrlLoading=" + overrideUrl);
-            override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
-                    overrideUrl);
-        } else {
-            Intent intent = new Intent(Intent.ACTION_VIEW,
-                    Uri.parse(overrideUrl));
-            intent.addCategory(Intent.CATEGORY_BROWSABLE);
-            // If another application is running a WebView and launches the
-            // Browser through this Intent, we want to reuse the same window if
-            // possible.
-            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
-                    mContext.getPackageName());
-            try {
-                mContext.startActivity(intent);
-                override = true;
-            } catch (ActivityNotFoundException ex) {
-                // If no application can handle the URL, assume that the
-                // browser can handle it.
-            }
-        }
-        return override;
-    }
-
-    /**
-     * Called by UI side.
-     */
-    public boolean uiOverrideKeyEvent(KeyEvent event) {
-        if (mWebViewClient != null) {
-            return mWebViewClient.shouldOverrideKeyEvent(mWebView.getWebView(), event);
-        }
-        return false;
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        // We don't have to do synchronization because this function operates
-        // in the UI thread. The WebViewClient and WebChromeClient functions
-        // that check for a non-null callback are ok because java ensures atomic
-        // 32-bit reads and writes.
-        if (messagesBlocked()) {
-            synchronized (this) {
-                notify();
-            }
-            return;
-        }
-        switch (msg.what) {
-            case PAGE_STARTED:
-                String startedUrl = msg.getData().getString("url");
-                mWebView.onPageStarted(startedUrl);
-                if (mWebViewClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onPageStarted=" + startedUrl);
-                    mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
-                            (Bitmap) msg.obj);
-                }
-                break;
-
-            case PAGE_FINISHED:
-                String finishedUrl = (String) msg.obj;
-                mWebView.onPageFinished(finishedUrl);
-                if (mWebViewClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onPageFinished=" + finishedUrl);
-                    mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
-                }
-                break;
-
-            case RECEIVED_ICON:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onReceivedIcon");
-                    mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
-                }
-                break;
-
-            case RECEIVED_TOUCH_ICON_URL:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onReceivedTouchIconUrl");
-                    mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
-                            (String) msg.obj, msg.arg1 == 1);
-                }
-                break;
-
-            case RECEIVED_TITLE:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onReceivedTitle");
-                    mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
-                            (String) msg.obj);
-                }
-                break;
-
-            case REPORT_ERROR:
-                if (mWebViewClient != null) {
-                    int reasonCode = msg.arg1;
-                    final String description  = msg.getData().getString("description");
-                    final String failUrl  = msg.getData().getString("failingUrl");
-                    if (TRACE) Log.d(LOGTAG, "onReceivedError=" + failUrl);
-                    mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
-                            description, failUrl);
-                }
-                break;
-
-            case RESEND_POST_DATA:
-                Message resend =
-                        (Message) msg.getData().getParcelable("resend");
-                Message dontResend =
-                        (Message) msg.getData().getParcelable("dontResend");
-                if (mWebViewClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onFormResubmission");
-                    mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
-                            resend);
-                } else {
-                    dontResend.sendToTarget();
-                }
-                break;
-
-            case OVERRIDE_URL:
-                String overrideUrl = msg.getData().getString("url");
-                boolean override = uiOverrideUrlLoading(overrideUrl);
-                ResultTransport<Boolean> result =
-                        (ResultTransport<Boolean>) msg.obj;
-                synchronized (this) {
-                    result.setResult(override);
-                    notify();
-                }
-                break;
-
-            case AUTH_REQUEST:
-                if (mWebViewClient != null) {
-                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
-                    String host = msg.getData().getString("host");
-                    String realm = msg.getData().getString("realm");
-                    if (TRACE) Log.d(LOGTAG, "onReceivedHttpAuthRequest");
-                    mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
-                            host, realm);
-                }
-                break;
-
-            case SSL_ERROR:
-                if (mWebViewClient != null) {
-                    HashMap<String, Object> map =
-                        (HashMap<String, Object>) msg.obj;
-                    if (TRACE) Log.d(LOGTAG, "onReceivedSslError");
-                    mWebViewClient.onReceivedSslError(mWebView.getWebView(),
-                            (SslErrorHandler) map.get("handler"),
-                            (SslError) map.get("error"));
-                }
-                break;
-
-            case PROCEEDED_AFTER_SSL_ERROR:
-                if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
-                    if (TRACE) Log.d(LOGTAG, "onProceededAfterSslError");
-                    ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
-                            mWebView.getWebView(),
-                            (SslError) msg.obj);
-                }
-                break;
-
-            case CLIENT_CERT_REQUEST:
-                if (mWebViewClient != null  && mWebViewClient instanceof WebViewClientClassicExt) {
-                    if (TRACE) Log.d(LOGTAG, "onReceivedClientCertRequest");
-                    HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
-                    ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
-                            mWebView.getWebView(),
-                            (ClientCertRequestHandler) map.get("handler"),
-                            (String) map.get("host_and_port"));
-                }
-                break;
-
-            case PROGRESS:
-                // Synchronize to ensure mLatestProgress is not modified after
-                // setProgress is called and before mProgressUpdatePending is
-                // changed.
-                synchronized (this) {
-                    if (mWebChromeClient != null) {
-                        if (TRACE) Log.d(LOGTAG, "onProgressChanged=" + mLatestProgress);
-                        mWebChromeClient.onProgressChanged(mWebView.getWebView(),
-                                mLatestProgress);
-                    }
-                    mProgressUpdatePending = false;
-                }
-                break;
-
-            case UPDATE_VISITED:
-                if (mWebViewClient != null) {
-                    String url = (String) msg.obj;
-                    if (TRACE) Log.d(LOGTAG, "doUpdateVisitedHistory=" + url);
-                    mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
-                            url, msg.arg1 != 0);
-                }
-                break;
-
-            case LOAD_RESOURCE:
-                if (mWebViewClient != null) {
-                    String url = (String) msg.obj;
-                    if (TRACE) Log.d(LOGTAG, "onLoadResource=" + url);
-                    mWebViewClient.onLoadResource(mWebView.getWebView(), url);
-                }
-                break;
-
-            case DOWNLOAD_FILE:
-                if (mDownloadListener != null) {
-                    String url = msg.getData().getString("url");
-                    String userAgent = msg.getData().getString("userAgent");
-                    String contentDisposition =
-                        msg.getData().getString("contentDisposition");
-                    String mimetype = msg.getData().getString("mimetype");
-                    String referer = msg.getData().getString("referer");
-                    Long contentLength = msg.getData().getLong("contentLength");
-
-                    if (TRACE) Log.d(LOGTAG, "onDownloadStart");
-                    if (mDownloadListener instanceof BrowserDownloadListener) {
-                        ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
-                             userAgent, contentDisposition, mimetype, referer, contentLength);
-                    } else {
-                        mDownloadListener.onDownloadStart(url, userAgent,
-                             contentDisposition, mimetype, contentLength);
-                    }
-                }
-                break;
-
-            case CREATE_WINDOW:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onCreateWindow");
-                    if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
-                                msg.arg1 == 1, msg.arg2 == 1,
-                                (Message) msg.obj)) {
-                        synchronized (this) {
-                            notify();
-                        }
-                    }
-                    mWebView.dismissZoomControl();
-                }
-                break;
-
-            case REQUEST_FOCUS:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onRequestFocus");
-                    mWebChromeClient.onRequestFocus(mWebView.getWebView());
-                }
-                break;
-
-            case CLOSE_WINDOW:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onCloseWindow");
-                    mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
-                }
-                break;
-
-            case SAVE_PASSWORD:
-                Bundle bundle = msg.getData();
-                String schemePlusHost = bundle.getString("host");
-                String username = bundle.getString("username");
-                String password = bundle.getString("password");
-                // If the client returned false it means that the notify message
-                // will not be sent and we should notify WebCore ourselves.
-                if (!mWebView.onSavePassword(schemePlusHost, username, password,
-                            (Message) msg.obj)) {
-                    synchronized (this) {
-                        notify();
-                    }
-                }
-                break;
-
-            case ASYNC_KEYEVENTS:
-                if (mWebViewClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onUnhandledKeyEvent");
-                    mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
-                            (KeyEvent) msg.obj);
-                }
-                break;
-
-            case EXCEEDED_DATABASE_QUOTA:
-                if (mWebChromeClient != null) {
-                    HashMap<String, Object> map =
-                            (HashMap<String, Object>) msg.obj;
-                    String databaseIdentifier =
-                            (String) map.get("databaseIdentifier");
-                    String url = (String) map.get("url");
-                    long quota =
-                            ((Long) map.get("quota")).longValue();
-                    long totalQuota =
-                            ((Long) map.get("totalQuota")).longValue();
-                    long estimatedDatabaseSize =
-                            ((Long) map.get("estimatedDatabaseSize")).longValue();
-                    WebStorage.QuotaUpdater quotaUpdater =
-                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
-
-                    if (TRACE) Log.d(LOGTAG, "onExceededDatabaseQuota");
-                    mWebChromeClient.onExceededDatabaseQuota(url,
-                            databaseIdentifier, quota, estimatedDatabaseSize,
-                            totalQuota, quotaUpdater);
-                }
-                break;
-
-            case REACHED_APPCACHE_MAXSIZE:
-                if (mWebChromeClient != null) {
-                    HashMap<String, Object> map =
-                            (HashMap<String, Object>) msg.obj;
-                    long requiredStorage =
-                            ((Long) map.get("requiredStorage")).longValue();
-                    long quota =
-                        ((Long) map.get("quota")).longValue();
-                    WebStorage.QuotaUpdater quotaUpdater =
-                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
-
-                    if (TRACE) Log.d(LOGTAG, "onReachedMaxAppCacheSize");
-                    mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
-                            quota, quotaUpdater);
-                }
-                break;
-
-            case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
-                if (mWebChromeClient != null) {
-                    HashMap<String, Object> map =
-                            (HashMap<String, Object>) msg.obj;
-                    String origin = (String) map.get("origin");
-                    GeolocationPermissions.Callback callback =
-                            (GeolocationPermissions.Callback)
-                            map.get("callback");
-                    if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsShowPrompt");
-                    mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
-                            callback);
-                }
-                break;
-
-            case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsHidePrompt");
-                    mWebChromeClient.onGeolocationPermissionsHidePrompt();
-                }
-                break;
-
-            case JS_DIALOG:
-                if (mWebChromeClient != null) {
-                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
-                    if (TRACE) Log.d(LOGTAG, "onJsAlert");
-                    if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
-                        helper.showDialog(mContext);
-                    }
-                    receiver.setReady();
-                }
-                break;
-
-            case JS_TIMEOUT:
-                if(mWebChromeClient != null) {
-                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
-                    final JsResult res = receiver.mJsResult;
-                    if (TRACE) Log.d(LOGTAG, "onJsTimeout");
-                    if (mWebChromeClient.onJsTimeout()) {
-                        res.confirm();
-                    } else {
-                        res.cancel();
-                    }
-                    receiver.setReady();
-                }
-                break;
-
-            case RECEIVED_CERTIFICATE:
-                mWebView.setCertificate((SslCertificate) msg.obj);
-                break;
-
-            case NOTIFY:
-                synchronized (this) {
-                    notify();
-                }
-                break;
-
-            case SCALE_CHANGED:
-                if (mWebViewClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onScaleChanged");
-                    mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
-                            .getFloat("old"), msg.getData().getFloat("new"));
-                }
-                break;
-
-            case SWITCH_OUT_HISTORY:
-                mWebView.switchOutDrawHistory();
-                break;
-
-            case ADD_MESSAGE_TO_CONSOLE:
-                if (mWebChromeClient == null) {
-                    break;
-                }
-                String message = msg.getData().getString("message");
-                String sourceID = msg.getData().getString("sourceID");
-                int lineNumber = msg.getData().getInt("lineNumber");
-                int msgLevel = msg.getData().getInt("msgLevel");
-                int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
-                // Sanity bounds check as we'll index an array with msgLevel
-                if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
-                    msgLevel = 0;
-                }
-
-                ConsoleMessage.MessageLevel messageLevel =
-                        ConsoleMessage.MessageLevel.values()[msgLevel];
-
-                if (TRACE) Log.d(LOGTAG, "onConsoleMessage");
-                if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
-                        lineNumber, messageLevel))) {
-                    // If false was returned the user did not provide their own console function so
-                    //  we should output some default messages to the system log.
-                    String logTag = "Web Console";
-                    String logMessage = message + " at " + sourceID + ":" + lineNumber;
-
-                    switch (messageLevel) {
-                        case TIP:
-                            Log.v(logTag, logMessage);
-                            break;
-                        case LOG:
-                            Log.i(logTag, logMessage);
-                            break;
-                        case WARNING:
-                            Log.w(logTag, logMessage);
-                            break;
-                        case ERROR:
-                            Log.e(logTag, logMessage);
-                            break;
-                        case DEBUG:
-                            Log.d(logTag, logMessage);
-                            break;
-                    }
-                }
-
-                break;
-
-            case GET_VISITED_HISTORY:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "getVisitedHistory");
-                    mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
-                }
-                break;
-
-            case OPEN_FILE_CHOOSER:
-                if (mWebChromeClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "openFileChooser");
-                    UploadFileMessageData data = (UploadFileMessageData)msg.obj;
-                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
-                            data.getCapture());
-                }
-                break;
-
-            case ADD_HISTORY_ITEM:
-                if (mWebBackForwardListClient != null) {
-                    if (TRACE) Log.d(LOGTAG, "onNewHistoryItem");
-                    mWebBackForwardListClient.onNewHistoryItem(
-                            (WebHistoryItem) msg.obj);
-                }
-                break;
-
-            case HISTORY_INDEX_CHANGED:
-                if (mWebBackForwardListClient != null) {
-                    mWebBackForwardListClient.onIndexChanged(
-                            (WebHistoryItem) msg.obj, msg.arg1);
-                }
-                break;
-            case AUTH_CREDENTIALS: {
-                String host = msg.getData().getString("host");
-                String realm = msg.getData().getString("realm");
-                username = msg.getData().getString("username");
-                password = msg.getData().getString("password");
-                mWebView.setHttpAuthUsernamePassword(
-                        host, realm, username, password);
-                break;
-            }
-            case AUTO_LOGIN: {
-                if (mWebViewClient != null) {
-                    String realm = msg.getData().getString("realm");
-                    String account = msg.getData().getString("account");
-                    String args = msg.getData().getString("args");
-                    if (TRACE) Log.d(LOGTAG, "onReceivedLoginRequest");
-                    mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
-                            account, args);
-                }
-                break;
-            }
-        }
-    }
-
-    /**
-     * Return the latest progress.
-     */
-    public int getProgress() {
-        return mLatestProgress;
-    }
-
-    /**
-     * Called by WebCore side to switch out of history Picture drawing mode
-     */
-    void switchOutDrawHistory() {
-        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
-    }
-
-    //--------------------------------------------------------------------------
-    // WebViewClient functions.
-    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
-    // it is not necessary to include it here.
-    //--------------------------------------------------------------------------
-
-    // Performance probe
-    private static final boolean PERF_PROBE = false;
-    private long mWebCoreThreadTime;
-    private long mWebCoreIdleTime;
-
-    /*
-     * If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
-     * startWait() and finishWait() should be called before and after wait().
-
-    private WaitCallback mWaitCallback = null;
-    public static interface WaitCallback {
-        void startWait();
-        void finishWait();
-    }
-    public final void setWaitCallback(WaitCallback callback) {
-        mWaitCallback = callback;
-    }
-    */
-
-    // un-comment this block if PERF_PROBE is true
-    /*
-    private IdleCallback mIdleCallback = new IdleCallback();
-
-    private final class IdleCallback implements MessageQueue.WaitCallback {
-        private long mStartTime = 0;
-
-        public void finishWait() {
-            mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
-        }
-
-        public void startWait() {
-            mStartTime = SystemClock.uptimeMillis();
-        }
-    }
-    */
-
-    public void onPageStarted(String url, Bitmap favicon) {
-        // We need to send the message even if no WebViewClient is set, because we need to call
-        // WebView.onPageStarted().
-
-        // Performance probe
-        if (PERF_PROBE) {
-            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
-            mWebCoreIdleTime = 0;
-            // un-comment this if PERF_PROBE is true
-//            Looper.myQueue().setWaitCallback(mIdleCallback);
-        }
-        Message msg = obtainMessage(PAGE_STARTED);
-        msg.obj = favicon;
-        msg.getData().putString("url", url);
-        sendMessage(msg);
-    }
-
-    public void onPageFinished(String url) {
-        // Performance probe
-        if (PERF_PROBE) {
-            // un-comment this if PERF_PROBE is true
-//            Looper.myQueue().setWaitCallback(null);
-            Log.d("WebCore", "WebCore thread used " +
-                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
-                    + " ms and idled " + mWebCoreIdleTime + " ms");
-        }
-        Message msg = obtainMessage(PAGE_FINISHED, url);
-        sendMessage(msg);
-    }
-
-    // Because this method is public and because CallbackProxy is mistakenly
-    // party of the public classes, we cannot remove this method.
-    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
-        // deprecated.
-    }
-
-    public void onReceivedError(int errorCode, String description,
-            String failingUrl) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
-
-        Message msg = obtainMessage(REPORT_ERROR);
-        msg.arg1 = errorCode;
-        msg.getData().putString("description", description);
-        msg.getData().putString("failingUrl", failingUrl);
-        sendMessage(msg);
-    }
-
-    public void onFormResubmission(Message dontResend,
-            Message resend) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            dontResend.sendToTarget();
-            return;
-        }
-
-        Message msg = obtainMessage(RESEND_POST_DATA);
-        Bundle bundle = msg.getData();
-        bundle.putParcelable("resend", resend);
-        bundle.putParcelable("dontResend", dontResend);
-        sendMessage(msg);
-    }
-
-    /**
-     * Called by the WebCore side
-     */
-    public boolean shouldOverrideUrlLoading(String url) {
-        // We have a default behavior if no client exists so always send the
-        // message.
-        ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
-        Message msg = obtainMessage(OVERRIDE_URL);
-        msg.getData().putString("url", url);
-        msg.obj = res;
-        sendMessageToUiThreadSync(msg);
-        return res.getResult().booleanValue();
-    }
-
-    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
-            String hostName, String realmName) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            handler.cancel();
-            return;
-        }
-        Message msg = obtainMessage(AUTH_REQUEST, handler);
-        msg.getData().putString("host", hostName);
-        msg.getData().putString("realm", realmName);
-        sendMessage(msg);
-    }
-
-    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            handler.cancel();
-            return;
-        }
-        Message msg = obtainMessage(SSL_ERROR);
-        HashMap<String, Object> map = new HashMap();
-        map.put("handler", handler);
-        map.put("error", error);
-        msg.obj = map;
-        sendMessage(msg);
-    }
-
-    public void onProceededAfterSslError(SslError error) {
-        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
-            return;
-        }
-        Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
-        msg.obj = error;
-        sendMessage(msg);
-    }
-
-    public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
-            handler.cancel();
-            return;
-        }
-        Message msg = obtainMessage(CLIENT_CERT_REQUEST);
-        HashMap<String, Object> map = new HashMap();
-        map.put("handler", handler);
-        map.put("host_and_port", host_and_port);
-        msg.obj = map;
-        sendMessage(msg);
-    }
-
-    public void onReceivedCertificate(SslCertificate certificate) {
-        // here, certificate can be null (if the site is not secure)
-        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
-    }
-
-    public void doUpdateVisitedHistory(String url, boolean isReload) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
-    }
-
-    WebResourceResponse shouldInterceptRequest(String url) {
-        if (mWebViewClient == null) {
-            return null;
-        }
-        // Note: This method does _not_ send a message.
-        if (TRACE) Log.d(LOGTAG, "shouldInterceptRequest=" + url);
-        WebResourceResponse r =
-                mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
-        if (r == null) {
-            sendMessage(obtainMessage(LOAD_RESOURCE, url));
-        }
-        return r;
-    }
-
-    public void onUnhandledKeyEvent(KeyEvent event) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
-    }
-
-    public void onScaleChanged(float oldScale, float newScale) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
-        Message msg = obtainMessage(SCALE_CHANGED);
-        Bundle bundle = msg.getData();
-        bundle.putFloat("old", oldScale);
-        bundle.putFloat("new", newScale);
-        sendMessage(msg);
-    }
-
-    void onReceivedLoginRequest(String realm, String account, String args) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
-        Message msg = obtainMessage(AUTO_LOGIN);
-        Bundle bundle = msg.getData();
-        bundle.putString("realm", realm);
-        bundle.putString("account", account);
-        bundle.putString("args", args);
-        sendMessage(msg);
-    }
-
-    //--------------------------------------------------------------------------
-    // DownloadListener functions.
-    //--------------------------------------------------------------------------
-
-    /**
-     * Starts a download if a download listener has been registered, otherwise
-     * return false.
-     */
-    public boolean onDownloadStart(String url, String userAgent,
-            String contentDisposition, String mimetype, String referer,
-            long contentLength) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mDownloadListener == null) {
-            // Cancel the download if there is no browser client.
-            return false;
-        }
-
-        Message msg = obtainMessage(DOWNLOAD_FILE);
-        Bundle bundle = msg.getData();
-        bundle.putString("url", url);
-        bundle.putString("userAgent", userAgent);
-        bundle.putString("mimetype", mimetype);
-        bundle.putString("referer", referer);
-        bundle.putLong("contentLength", contentLength);
-        bundle.putString("contentDisposition", contentDisposition);
-        sendMessage(msg);
-        return true;
-    }
-
-
-    //--------------------------------------------------------------------------
-    // WebView specific functions that do not interact with a client. These
-    // functions just need to operate within the UI thread.
-    //--------------------------------------------------------------------------
-
-    public boolean onSavePassword(String schemePlusHost, String username,
-            String password, Message resumeMsg) {
-        // resumeMsg should be null at this point because we want to create it
-        // within the CallbackProxy.
-        if (DebugFlags.CALLBACK_PROXY) {
-            junit.framework.Assert.assertNull(resumeMsg);
-        }
-        resumeMsg = obtainMessage(NOTIFY);
-
-        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
-        Bundle bundle = msg.getData();
-        bundle.putString("host", schemePlusHost);
-        bundle.putString("username", username);
-        bundle.putString("password", password);
-        sendMessageToUiThreadSync(msg);
-        // Doesn't matter here
-        return false;
-    }
-
-    public void onReceivedHttpAuthCredentials(String host, String realm,
-            String username, String password) {
-        Message msg = obtainMessage(AUTH_CREDENTIALS);
-        msg.getData().putString("host", host);
-        msg.getData().putString("realm", realm);
-        msg.getData().putString("username", username);
-        msg.getData().putString("password", password);
-        sendMessage(msg);
-    }
-
-    //--------------------------------------------------------------------------
-    // WebChromeClient methods
-    //--------------------------------------------------------------------------
-
-    public void onProgressChanged(int newProgress) {
-        // Synchronize so that mLatestProgress is up-to-date.
-        synchronized (this) {
-            // update mLatestProgress even mWebChromeClient is null as
-            // WebView.getProgress() needs it
-            if (mLatestProgress == newProgress) {
-                return;
-            }
-            mLatestProgress = newProgress;
-            if (mWebChromeClient == null) {
-                return;
-            }
-            if (!mProgressUpdatePending) {
-                sendEmptyMessage(PROGRESS);
-                mProgressUpdatePending = true;
-            }
-        }
-    }
-
-    public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return null;
-        }
-
-        WebView.WebViewTransport transport =
-            mWebView.getWebView().new WebViewTransport();
-        final Message msg = obtainMessage(NOTIFY);
-        msg.obj = transport;
-        sendMessageToUiThreadSync(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
-                userGesture ? 1 : 0, msg));
-        WebViewClassic w = WebViewClassic.fromWebView(transport.getWebView());
-        if (w != null) {
-            WebViewCore core = w.getWebViewCore();
-            // If WebView.destroy() has been called, core may be null.  Skip
-            // initialization in that case and return null.
-            if (core != null) {
-                core.initializeSubwindow();
-                return core.getBrowserFrame();
-            }
-        }
-        return null;
-    }
-
-    public void onRequestFocus() {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-
-        sendEmptyMessage(REQUEST_FOCUS);
-    }
-
-    public void onCloseWindow(WebViewClassic window) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(CLOSE_WINDOW, window));
-    }
-
-    public void onReceivedIcon(Bitmap icon) {
-        // The current item might be null if the icon was already stored in the
-        // database and this is a new WebView.
-        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
-        if (i != null) {
-            i.setFavicon(icon);
-        }
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(RECEIVED_ICON, icon));
-    }
-
-    /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
-        // We should have a current item but we do not want to crash so check
-        // for null.
-        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
-        if (i != null) {
-            i.setTouchIconUrl(url, precomposed);
-        }
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
-                precomposed ? 1 : 0, 0, url));
-    }
-
-    public void onReceivedTitle(String title) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-        sendMessage(obtainMessage(RECEIVED_TITLE, title));
-    }
-
-    public void onJsAlert(String url, String message) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return;
-        }
-        JsResultReceiver result = new JsResultReceiver();
-        Message alert = obtainMessage(JS_DIALOG, result);
-        alert.getData().putString("message", message);
-        alert.getData().putString("url", url);
-        alert.getData().putInt("type", JsDialogHelper.ALERT);
-        sendMessageToUiThreadSync(alert);
-    }
-
-    public boolean onJsConfirm(String url, String message) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return false;
-        }
-        JsResultReceiver result = new JsResultReceiver();
-        Message confirm = obtainMessage(JS_DIALOG, result);
-        confirm.getData().putString("message", message);
-        confirm.getData().putString("url", url);
-        confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
-        sendMessageToUiThreadSync(confirm);
-        return result.mJsResult.getResult();
-    }
-
-    public String onJsPrompt(String url, String message, String defaultValue) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return null;
-        }
-        JsResultReceiver result = new JsResultReceiver();
-        Message prompt = obtainMessage(JS_DIALOG, result);
-        prompt.getData().putString("message", message);
-        prompt.getData().putString("default", defaultValue);
-        prompt.getData().putString("url", url);
-        prompt.getData().putInt("type", JsDialogHelper.PROMPT);
-        sendMessageToUiThreadSync(prompt);
-        return result.mJsResult.getStringResult();
-    }
-
-    public boolean onJsBeforeUnload(String url, String message) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebChromeClient == null) {
-            return true;
-        }
-        JsResultReceiver result = new JsResultReceiver();
-        Message unload = obtainMessage(JS_DIALOG, result);
-        unload.getData().putString("message", message);
-        unload.getData().putString("url", url);
-        unload.getData().putInt("type", JsDialogHelper.UNLOAD);
-        sendMessageToUiThreadSync(unload);
-        return result.mJsResult.getResult();
-    }
-
-    /**
-     * Called by WebViewCore to inform the Java side that the current origin
-     * has overflowed it's database quota. Called in the WebCore thread so
-     * posts a message to the UI thread that will prompt the WebChromeClient
-     * for what to do. On return back to C++ side, the WebCore thread will
-     * sleep pending a new quota value.
-     * @param url The URL that caused the quota overflow.
-     * @param databaseIdentifier The identifier of the database that the
-     *     transaction that caused the overflow was running on.
-     * @param quota The current quota the origin is allowed.
-     * @param estimatedDatabaseSize The estimated size of the database.
-     * @param totalQuota is the sum of all origins' quota.
-     * @param quotaUpdater An instance of a class encapsulating a callback
-     *     to WebViewCore to run when the decision to allow or deny more
-     *     quota has been made.
-     */
-    public void onExceededDatabaseQuota(
-            String url, String databaseIdentifier, long quota,
-            long estimatedDatabaseSize, long totalQuota,
-            WebStorage.QuotaUpdater quotaUpdater) {
-        if (mWebChromeClient == null) {
-            // Native-side logic prevents the quota being updated to a smaller
-            // value.
-            quotaUpdater.updateQuota(quota);
-            return;
-        }
-
-        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
-        HashMap<String, Object> map = new HashMap();
-        map.put("databaseIdentifier", databaseIdentifier);
-        map.put("url", url);
-        map.put("quota", quota);
-        map.put("estimatedDatabaseSize", estimatedDatabaseSize);
-        map.put("totalQuota", totalQuota);
-        map.put("quotaUpdater", quotaUpdater);
-        exceededQuota.obj = map;
-        sendMessage(exceededQuota);
-    }
-
-    /**
-     * Called by WebViewCore to inform the Java side that the appcache has
-     * exceeded its max size.
-     * @param requiredStorage is the amount of storage, in bytes, that would be
-     * needed in order for the last appcache operation to succeed.
-     * @param quota is the current quota (for all origins).
-     * @param quotaUpdater An instance of a class encapsulating a callback
-     * to WebViewCore to run when the decision to allow or deny a bigger
-     * app cache size has been made.
-     */
-    public void onReachedMaxAppCacheSize(long requiredStorage,
-            long quota, WebStorage.QuotaUpdater quotaUpdater) {
-        if (mWebChromeClient == null) {
-            // Native-side logic prevents the quota being updated to a smaller
-            // value.
-            quotaUpdater.updateQuota(quota);
-            return;
-        }
-
-        Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
-        HashMap<String, Object> map = new HashMap();
-        map.put("requiredStorage", requiredStorage);
-        map.put("quota", quota);
-        map.put("quotaUpdater", quotaUpdater);
-        msg.obj = map;
-        sendMessage(msg);
-    }
-
-    /**
-     * Called by WebViewCore to instruct the browser to display a prompt to ask
-     * the user to set the Geolocation permission state for the given origin.
-     * @param origin The origin requesting Geolocation permsissions.
-     * @param callback The callback to call once a permission state has been
-     *     obtained.
-     */
-    public void onGeolocationPermissionsShowPrompt(String origin,
-            GeolocationPermissions.Callback callback) {
-        if (mWebChromeClient == null) {
-            return;
-        }
-
-        Message showMessage =
-                obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
-        HashMap<String, Object> map = new HashMap();
-        map.put("origin", origin);
-        map.put("callback", callback);
-        showMessage.obj = map;
-        sendMessage(showMessage);
-    }
-
-    /**
-     * Called by WebViewCore to instruct the browser to hide the Geolocation
-     * permissions prompt.
-     */
-    public void onGeolocationPermissionsHidePrompt() {
-        if (mWebChromeClient == null) {
-            return;
-        }
-
-        Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
-        sendMessage(hideMessage);
-    }
-
-    /**
-     * Called by WebViewCore when we have a message to be added to the JavaScript
-     * error console. Sends a message to the Java side with the details.
-     * @param message The message to add to the console.
-     * @param lineNumber The lineNumber of the source file on which the error
-     *     occurred.
-     * @param sourceID The filename of the source file in which the error
-     *     occurred.
-     * @param msgLevel The message level, corresponding to the MessageLevel enum in
-     *     WebCore/page/Console.h
-     */
-    public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
-        if (mWebChromeClient == null) {
-            return;
-        }
-
-        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
-        msg.getData().putString("message", message);
-        msg.getData().putString("sourceID", sourceID);
-        msg.getData().putInt("lineNumber", lineNumber);
-        msg.getData().putInt("msgLevel", msgLevel);
-        sendMessage(msg);
-    }
-
-    public boolean onJsTimeout() {
-        //always interrupt timedout JS by default
-        if (mWebChromeClient == null) {
-            return true;
-        }
-        JsResultReceiver result = new JsResultReceiver();
-        Message timeout = obtainMessage(JS_TIMEOUT, result);
-        sendMessageToUiThreadSync(timeout);
-        return result.mJsResult.getResult();
-    }
-
-    public void getVisitedHistory(ValueCallback<String[]> callback) {
-        if (mWebChromeClient == null) {
-            return;
-        }
-        Message msg = obtainMessage(GET_VISITED_HISTORY);
-        msg.obj = callback;
-        sendMessage(msg);
-    }
-
-    private static class UploadFileMessageData {
-        private UploadFile mCallback;
-        private String mAcceptType;
-        private String mCapture;
-
-        public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
-            mCallback = uploadFile;
-            mAcceptType = acceptType;
-            mCapture = capture;
-        }
-
-        public UploadFile getUploadFile() {
-            return mCallback;
-        }
-
-        public String getAcceptType() {
-            return mAcceptType;
-        }
-
-        public String getCapture() {
-            return mCapture;
-        }
-    }
-
-    private class UploadFile implements ValueCallback<Uri> {
-        private Uri mValue;
-        public void onReceiveValue(Uri value) {
-            mValue = value;
-            synchronized (CallbackProxy.this) {
-                CallbackProxy.this.notify();
-            }
-        }
-        public Uri getResult() {
-            return mValue;
-        }
-    }
-
-    /**
-     * Called by WebViewCore to open a file chooser.
-     */
-    /* package */ Uri openFileChooser(String acceptType, String capture) {
-        if (mWebChromeClient == null) {
-            return null;
-        }
-        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
-        UploadFile uploadFile = new UploadFile();
-        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
-        myMessage.obj = data;
-        sendMessageToUiThreadSync(myMessage);
-        return uploadFile.getResult();
-    }
-
-    void onNewHistoryItem(WebHistoryItem item) {
-        if (mWebBackForwardListClient == null) {
-            return;
-        }
-        Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
-        sendMessage(msg);
-    }
-
-    void onIndexChanged(WebHistoryItem item, int index) {
-        if (mWebBackForwardListClient == null) {
-            return;
-        }
-        Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
-        sendMessage(msg);
-    }
-
-    private synchronized void sendMessageToUiThreadSync(Message msg) {
-        sendMessage(msg);
-        WebCoreThreadWatchdog.pause();
-        try {
-            wait();
-        } catch (InterruptedException e) {
-            Log.e(LOGTAG, "Caught exception waiting for synchronous UI message to be processed");
-            Log.e(LOGTAG, Log.getStackTraceString(e));
-        }
-        WebCoreThreadWatchdog.resume();
-    }
-}
diff --git a/core/java/android/webkit/CertTool.java b/core/java/android/webkit/CertTool.java
deleted file mode 100644
index e4d09a9..0000000
--- a/core/java/android/webkit/CertTool.java
+++ /dev/null
@@ -1,80 +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.
- */
-
-package android.webkit;
-
-import com.android.org.bouncycastle.asn1.ASN1Encoding;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest;
-import com.android.org.bouncycastle.util.encoders.Base64;
-
-import android.content.Context;
-import android.security.Credentials;
-import android.security.KeyChain;
-import android.util.Log;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.util.HashMap;
-
-final class CertTool {
-    private static final String LOGTAG = "CertTool";
-
-    private static final AlgorithmIdentifier MD5_WITH_RSA =
-            new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption);
-
-    private static HashMap<String, String> sCertificateTypeMap;
-    static {
-        sCertificateTypeMap = new HashMap<String, String>();
-        sCertificateTypeMap.put("application/x-x509-ca-cert", KeyChain.EXTRA_CERTIFICATE);
-        sCertificateTypeMap.put("application/x-x509-user-cert", KeyChain.EXTRA_CERTIFICATE);
-        sCertificateTypeMap.put("application/x-pkcs12", KeyChain.EXTRA_PKCS12);
-    }
-
-    static String[] getKeyStrengthList() {
-        return new String[] {"High Grade", "Medium Grade"};
-    }
-
-    static String getSignedPublicKey(Context context, int index, String challenge) {
-        try {
-            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
-            generator.initialize((index == 0) ? 2048 : 1024);
-            KeyPair pair = generator.genKeyPair();
-
-            NetscapeCertRequest request = new NetscapeCertRequest(challenge,
-                    MD5_WITH_RSA, pair.getPublic());
-            request.sign(pair.getPrivate());
-            byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER);
-
-            Credentials.getInstance().install(context, pair);
-            return new String(Base64.encode(signed));
-        } catch (Exception e) {
-            Log.w(LOGTAG, e);
-        }
-        return null;
-    }
-
-    static void addCertificate(Context context, String type, byte[] value) {
-        Credentials.getInstance().install(context, type, value);
-    }
-
-    static String getCertType(String mimeType) {
-        return sCertificateTypeMap.get(mimeType);
-    }
-
-    private CertTool() {}
-}
diff --git a/core/java/android/webkit/ClientCertRequestHandler.java b/core/java/android/webkit/ClientCertRequestHandler.java
deleted file mode 100644
index 8cab9a6..0000000
--- a/core/java/android/webkit/ClientCertRequestHandler.java
+++ /dev/null
@@ -1,127 +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.
- */
-
-package android.webkit;
-
-import android.os.Handler;
-import java.security.PrivateKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import com.android.org.conscrypt.OpenSSLKey;
-import com.android.org.conscrypt.OpenSSLKeyHolder;
-
-/**
- * ClientCertRequestHandler: class responsible for handling client
- * certificate requests.  This class is passed as a parameter to
- * BrowserCallback.displayClientCertRequestDialog and is meant to
- * receive the user's response.
- *
- * @hide
- */
-public final class ClientCertRequestHandler extends Handler {
-
-    private final BrowserFrame mBrowserFrame;
-    private final int mHandle;
-    private final String mHostAndPort;
-    private final SslClientCertLookupTable mTable;
-    ClientCertRequestHandler(BrowserFrame browserFrame,
-                             int handle,
-                             String host_and_port,
-                             SslClientCertLookupTable table) {
-        mBrowserFrame = browserFrame;
-        mHandle = handle;
-        mHostAndPort = host_and_port;
-        mTable = table;
-    }
-
-    private static byte[][] encodeCertificates(X509Certificate[] certificates)
-            throws CertificateEncodingException {
-        byte[][] certificateBytes = new byte[certificates.length][];
-        for (int i = 0; i < certificates.length; i++) {
-            certificateBytes[i] = certificates[i].getEncoded();
-        }
-        return certificateBytes;
-    }
-
-    /**
-     * Proceed with the specified private key and client certificate chain.
-     */
-    public void proceed(PrivateKey privateKey, X509Certificate[] chain) {
-        try {
-            byte[][] chainBytes = encodeCertificates(chain);
-            mTable.Allow(mHostAndPort, privateKey, chainBytes);
-
-            if (privateKey instanceof OpenSSLKeyHolder) {
-                OpenSSLKey pkey = ((OpenSSLKeyHolder) privateKey).getOpenSSLKey();
-                setSslClientCertFromCtx(pkey.getPkeyContext(), chainBytes);
-            } else {
-                setSslClientCertFromPKCS8(privateKey.getEncoded(), chainBytes);
-            }
-        } catch (CertificateEncodingException e) {
-            post(new Runnable() {
-                    public void run() {
-                        mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
-                        return;
-                    }
-                });
-        }
-    }
-
-    /**
-     * Proceed with the specified private key bytes and client certificate chain.
-     */
-    private void setSslClientCertFromCtx(final long ctx, final byte[][] chainBytes) {
-        post(new Runnable() {
-                public void run() {
-                    mBrowserFrame.nativeSslClientCert(mHandle, ctx, chainBytes);
-                }
-            });
-    }
-
-    /**
-     * Proceed with the specified private key context and client certificate chain.
-     */
-    private void setSslClientCertFromPKCS8(final byte[] key, final byte[][] chainBytes) {
-        post(new Runnable() {
-                public void run() {
-                    mBrowserFrame.nativeSslClientCert(mHandle, key, chainBytes);
-                }
-            });
-    }
-
-    /**
-     * Igore the request for now, the user may be prompted again.
-     */
-    public void ignore() {
-        post(new Runnable() {
-                public void run() {
-                    mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
-                }
-            });
-    }
-
-    /**
-     * Cancel this request, remember the users negative choice.
-     */
-    public void cancel() {
-        mTable.Deny(mHostAndPort);
-        post(new Runnable() {
-                public void run() {
-                    mBrowserFrame.nativeSslClientCert(mHandle, 0, null);
-                }
-            });
-    }
-}
diff --git a/core/java/android/webkit/CookieManagerClassic.java b/core/java/android/webkit/CookieManagerClassic.java
deleted file mode 100644
index 36159e1..0000000
--- a/core/java/android/webkit/CookieManagerClassic.java
+++ /dev/null
@@ -1,184 +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;
-
-import android.net.ParseException;
-import android.net.WebAddress;
-import android.os.AsyncTask;
-import android.util.Log;
-
-class CookieManagerClassic extends CookieManager {
-
-    private static CookieManagerClassic sRef;
-
-    private static final String LOGTAG = "webkit";
-
-    private int mPendingCookieOperations = 0;
-
-    private CookieManagerClassic() {
-    }
-
-    public static synchronized CookieManagerClassic getInstance() {
-        if (sRef == null) {
-            sRef = new CookieManagerClassic();
-        }
-        return sRef;
-    }
-
-    @Override
-    public synchronized void setAcceptCookie(boolean accept) {
-        nativeSetAcceptCookie(accept);
-    }
-
-    @Override
-    public synchronized boolean acceptCookie() {
-        return nativeAcceptCookie();
-    }
-
-    @Override
-    public void setCookie(String url, String value) {
-        setCookie(url, value, false);
-    }
-
-    /**
-     * See {@link #setCookie(String, String)}
-     * @param url The URL for which the cookie is set
-     * @param value The value of the cookie, as a string, using the format of
-     *              the 'Set-Cookie' HTTP response header
-     * @param privateBrowsing Whether to use the private browsing cookie jar
-     */
-    void setCookie(String url, String value, boolean privateBrowsing) {
-        WebAddress uri;
-        try {
-            uri = new WebAddress(url);
-        } catch (ParseException ex) {
-            Log.e(LOGTAG, "Bad address: " + url);
-            return;
-        }
-
-        nativeSetCookie(uri.toString(), value, privateBrowsing);
-    }
-
-    @Override
-    public String getCookie(String url) {
-        return getCookie(url, false);
-    }
-
-    @Override
-    public String getCookie(String url, boolean privateBrowsing) {
-        WebAddress uri;
-        try {
-            uri = new WebAddress(url);
-        } catch (ParseException ex) {
-            Log.e(LOGTAG, "Bad address: " + url);
-            return null;
-        }
-
-        return nativeGetCookie(uri.toString(), privateBrowsing);
-    }
-
-    @Override
-    public synchronized String getCookie(WebAddress uri) {
-        return nativeGetCookie(uri.toString(), false);
-    }
-
-    /**
-     * Waits for pending operations to completed.
-     */
-    void waitForCookieOperationsToComplete() {
-        // Note that this function is applicable for both the java
-        // and native http stacks, and works correctly with either.
-        synchronized (this) {
-            while (mPendingCookieOperations > 0) {
-                try {
-                    wait();
-                } catch (InterruptedException e) { }
-            }
-        }
-    }
-
-    private synchronized void signalCookieOperationsComplete() {
-        mPendingCookieOperations--;
-        assert mPendingCookieOperations > -1;
-        notify();
-    }
-
-    private synchronized void signalCookieOperationsStart() {
-        mPendingCookieOperations++;
-    }
-
-    @Override
-    public void removeSessionCookie() {
-        signalCookieOperationsStart();
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... none) {
-                nativeRemoveSessionCookie();
-                signalCookieOperationsComplete();
-                return null;
-            }
-        }.execute();
-    }
-
-    @Override
-    public void removeAllCookie() {
-        nativeRemoveAllCookie();
-    }
-
-    @Override
-    public synchronized boolean hasCookies() {
-        return hasCookies(false);
-    }
-
-    @Override
-    public synchronized boolean hasCookies(boolean privateBrowsing) {
-        return nativeHasCookies(privateBrowsing);
-    }
-
-    @Override
-    public void removeExpiredCookie() {
-        nativeRemoveExpiredCookie();
-    }
-
-    @Override
-    protected void flushCookieStore() {
-        nativeFlushCookieStore();
-    }
-
-    @Override
-    protected boolean allowFileSchemeCookiesImpl() {
-        return nativeAcceptFileSchemeCookies();
-    }
-
-    @Override
-    protected void setAcceptFileSchemeCookiesImpl(boolean accept) {
-        nativeSetAcceptFileSchemeCookies(accept);
-    }
-
-    // Native functions
-    private static native boolean nativeAcceptCookie();
-    private static native String nativeGetCookie(String url, boolean privateBrowsing);
-    private static native boolean nativeHasCookies(boolean privateBrowsing);
-    private static native void nativeRemoveAllCookie();
-    private static native void nativeRemoveExpiredCookie();
-    private static native void nativeRemoveSessionCookie();
-    private static native void nativeSetAcceptCookie(boolean accept);
-    private static native void nativeSetCookie(String url, String value, boolean privateBrowsing);
-    private static native void nativeFlushCookieStore();
-    private static native boolean nativeAcceptFileSchemeCookies();
-    private static native void nativeSetAcceptFileSchemeCookies(boolean accept);
-}
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index 154a290..13aa43f 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -89,10 +89,6 @@
         if (context == null) {
             throw new IllegalArgumentException("Invalid context argument");
         }
-        // TODO: Remove this workaround after webview classic is no longer supported.
-        if (WebViewFactory.getProvider().getClass().getName().contains("WebViewClassic")) {
-            WebViewDatabase.getInstance(context);
-        }
 
         setGetInstanceIsAllowed();
         return getInstance();
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index 524f610..b5ca8c1 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -35,22 +35,4 @@
     public static final boolean URL_UTIL = false;
     public static final boolean WEB_SYNC_MANAGER = false;
 
-    // TODO: Delete these when WebViewClassic is moved
-    public static final boolean BROWSER_FRAME = false;
-    public static final boolean CACHE_MANAGER = false;
-    public static final boolean CALLBACK_PROXY = false;
-    public static final boolean COOKIE_MANAGER = false;
-    public static final boolean FRAME_LOADER = false;
-    public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE
-    public static final boolean LOAD_LISTENER = false;
-    public static final boolean MEASURE_PAGE_SWAP_FPS = false;
-    public static final boolean NETWORK = false;
-    public static final boolean SSL_ERROR_HANDLER = false;
-    public static final boolean STREAM_LOADER = false;
-    public static final boolean WEB_BACK_FORWARD_LIST = false;
-    public static final boolean WEB_SETTINGS = false;
-    public static final boolean WEB_VIEW = false;
-    public static final boolean WEB_VIEW_CORE = false;
-
-
 }
diff --git a/core/java/android/webkit/DeviceMotionAndOrientationManager.java b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
deleted file mode 100644
index ea1e9ff..0000000
--- a/core/java/android/webkit/DeviceMotionAndOrientationManager.java
+++ /dev/null
@@ -1,78 +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.
- */
-
-package android.webkit;
-
-/**
- * This class is simply a container for the methods used to implement DeviceMotion and
- * DeviceOrientation, including the mock DeviceOrientationClient for use in LayoutTests.
- *
- * This could be part of WebViewCore, but have moved it to its own class to
- * avoid bloat there.
- */
-final class DeviceMotionAndOrientationManager {
-    private WebViewCore mWebViewCore;
-
-    public DeviceMotionAndOrientationManager(WebViewCore webViewCore) {
-        mWebViewCore = webViewCore;
-    }
-
-    /**
-     * Sets that the Page for this WebViewCore should use a mock DeviceOrientation
-     * client.
-     */
-    public void setUseMock() {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        nativeSetUseMock(mWebViewCore);
-    }
-
-    /**
-     * Set the position for the mock DeviceOrientation service for this WebViewCore.
-     */
-    public void setMockOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta,
-            double beta, boolean canProvideGamma, double gamma) {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        nativeSetMockOrientation(mWebViewCore, canProvideAlpha, alpha, canProvideBeta, beta,
-                canProvideGamma, gamma);
-    }
-
-    // We only provide accelerationIncludingGravity.
-    public void onMotionChange(Double x, Double y, Double z, double interval) {
-        nativeOnMotionChange(mWebViewCore,
-                x != null, x != null ? x.doubleValue() : 0.0,
-                y != null, y != null ? y.doubleValue() : 0.0,
-                z != null, z != null ? z.doubleValue() : 0.0,
-                interval);
-    }
-    public void onOrientationChange(Double alpha, Double beta, Double gamma) {
-        nativeOnOrientationChange(mWebViewCore,
-                alpha != null, alpha != null ? alpha.doubleValue() : 0.0,
-                beta != null, beta != null ? beta.doubleValue() : 0.0,
-                gamma != null, gamma != null ? gamma.doubleValue() : 0.0);
-    }
-
-    // Native functions
-    private static native void nativeSetUseMock(WebViewCore webViewCore);
-    private static native void nativeSetMockOrientation(WebViewCore webViewCore,
-            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
-            boolean canProvideGamma, double gamma);
-    private static native void nativeOnMotionChange(WebViewCore webViewCore,
-            boolean canProvideX, double x, boolean canProvideY, double y,
-            boolean canProvideZ, double z, double interval);
-    private static native void nativeOnOrientationChange(WebViewCore webViewCore,
-            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
-            boolean canProvideGamma, double gamma);
-}
diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java
deleted file mode 100644
index 9121429..0000000
--- a/core/java/android/webkit/DeviceMotionService.java
+++ /dev/null
@@ -1,178 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Message;
-import android.webkit.DeviceMotionAndOrientationManager;
-import java.lang.Runnable;
-import java.util.List;
-
-
-final class DeviceMotionService implements SensorEventListener {
-    private DeviceMotionAndOrientationManager mManager;
-    private boolean mIsRunning;
-    private Handler mHandler;
-    private SensorManager mSensorManager;
-    private Context mContext;
-    private boolean mHaveSentErrorEvent;
-    private Runnable mUpdateRunnable;
-    private float mLastAcceleration[];
-
-    private static final int INTERVAL_MILLIS = 100;
-
-    public DeviceMotionService(DeviceMotionAndOrientationManager manager, Context context) {
-        mManager = manager;
-        assert(mManager != null);
-        mContext = context;
-        assert(mContext != null);
-     }
-
-    public void start() {
-        mIsRunning = true;
-        registerForSensor();
-    }
-
-    public void stop() {
-        mIsRunning = false;
-        stopSendingUpdates();
-        unregisterFromSensor();
-    }
-
-    public void suspend() {
-        if (mIsRunning) {
-            stopSendingUpdates();
-            unregisterFromSensor();
-        }
-    }
-
-    public void resume() {
-        if (mIsRunning) {
-            registerForSensor();
-        }
-    }
-
-    private void sendErrorEvent() {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        // The spec requires that each listener receives the error event only once.
-        if (mHaveSentErrorEvent)
-            return;
-        mHaveSentErrorEvent = true;
-        createHandler();
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-                if (mIsRunning) {
-                    // The special case of all nulls is used to signify a failure to get data.
-                    mManager.onMotionChange(null, null, null, 0.0);
-                }
-            }
-        });
-    }
-
-    private void createHandler() {
-        if (mHandler != null) {
-            return;
-        }
-
-        mHandler = new Handler();
-        mUpdateRunnable = new Runnable() {
-            @Override
-            public void run() {
-                assert mIsRunning;
-                mManager.onMotionChange(new Double(mLastAcceleration[0]),
-                        new Double(mLastAcceleration[1]), new Double(mLastAcceleration[2]),
-                        INTERVAL_MILLIS);
-                mHandler.postDelayed(mUpdateRunnable, INTERVAL_MILLIS);
-                // Now that we have successfully sent some data, reset whether we've sent an error.
-                mHaveSentErrorEvent = false;
-            }
-        };
-    }
-
-    private void startSendingUpdates() {
-        createHandler();
-        mUpdateRunnable.run();
-    }
-
-    private void stopSendingUpdates() {
-        mHandler.removeCallbacks(mUpdateRunnable);
-        mLastAcceleration = null;
-    }
-
-    private void registerForSensor() {
-        if (!registerForAccelerometerSensor()) {
-            sendErrorEvent();
-        }
-    }
-
-    private SensorManager getSensorManager() {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        if (mSensorManager == null) {
-            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
-        }
-        return mSensorManager;
-    }
-
-    private boolean registerForAccelerometerSensor() {
-        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
-        if (sensors.isEmpty()) {
-            return false;
-        }
-        createHandler();
-        // TODO: Consider handling multiple sensors.
-        return getSensorManager().registerListener(
-                this, sensors.get(0), SensorManager.SENSOR_DELAY_UI, mHandler);
-    }
-
-    private void unregisterFromSensor() {
-        getSensorManager().unregisterListener(this);
-    }
-
-    /**
-     * SensorEventListener implementation.
-     * Callbacks happen on the thread on which we registered - the WebCore thread.
-     */
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        assert(event.values.length == 3);
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        assert(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER);
-
-        // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
-        if (!mIsRunning) {
-            return;
-        }
-
-        boolean firstData = mLastAcceleration == null;
-        mLastAcceleration = event.values;
-        if (firstData) {
-            startSendingUpdates();
-        }
-    }
-
-    @Override
-    public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-    }
-}
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
deleted file mode 100644
index a4d240d..0000000
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ /dev/null
@@ -1,225 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.webkit.DeviceMotionAndOrientationManager;
-import java.lang.Runnable;
-import java.util.List;
-
-
-final class DeviceOrientationService implements SensorEventListener {
-    // The gravity vector expressed in the body frame.
-    private float[] mGravityVector;
-    // The geomagnetic vector expressed in the body frame.
-    private float[] mMagneticFieldVector;
-
-    private DeviceMotionAndOrientationManager mManager;
-    private boolean mIsRunning;
-    private Handler mHandler;
-    private SensorManager mSensorManager;
-    private Context mContext;
-    private Double mAlpha;
-    private Double mBeta;
-    private Double mGamma;
-    private boolean mHaveSentErrorEvent;
-
-    private static final double DELTA_DEGRESS = 1.0;
-
-    public DeviceOrientationService(DeviceMotionAndOrientationManager manager, Context context) {
-        mManager = manager;
-        assert(mManager != null);
-        mContext = context;
-        assert(mContext != null);
-     }
-
-    public void start() {
-        mIsRunning = true;
-        registerForSensors();
-    }
-
-    public void stop() {
-        mIsRunning = false;
-        unregisterFromSensors();
-    }
-
-    public void suspend() {
-        if (mIsRunning) {
-            unregisterFromSensors();
-        }
-    }
-
-    public void resume() {
-        if (mIsRunning) {
-            registerForSensors();
-        }
-    }
-
-    private void sendErrorEvent() {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        // The spec requires that each listener receives the error event only once.
-        if (mHaveSentErrorEvent)
-            return;
-        mHaveSentErrorEvent = true;
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-                if (mIsRunning) {
-                    // The special case of all nulls is used to signify a failure to get data.
-                    mManager.onOrientationChange(null, null, null);
-                }
-            }
-        });
-    }
-
-    private void registerForSensors() {
-        if (mHandler == null) {
-            mHandler = new Handler();
-        }
-        if (!registerForAccelerometerSensor() || !registerForMagneticFieldSensor()) {
-            unregisterFromSensors();
-            sendErrorEvent();
-        }
-    }
-
-    private void getOrientationUsingGetRotationMatrix() {
-        if (mGravityVector == null || mMagneticFieldVector == null) {
-            return;
-        }
-
-        // Get the rotation matrix.
-        // The rotation matrix that transforms from the body frame to the earth frame.
-        float[] deviceRotationMatrix = new float[9];
-        if (!SensorManager.getRotationMatrix(
-                deviceRotationMatrix, null, mGravityVector, mMagneticFieldVector)) {
-            return;
-        }
-
-        // Convert rotation matrix to rotation angles.
-        // Assuming that the rotations are appied in the order listed at
-        // http://developer.android.com/reference/android/hardware/SensorEvent.html#values
-        // the rotations are applied about the same axes and in the same order as required by the
-        // API. The only conversions are sign changes as follows.
-        // The angles are in radians
-        float[] rotationAngles = new float[3];
-        SensorManager.getOrientation(deviceRotationMatrix, rotationAngles);
-        double alpha = Math.toDegrees(-rotationAngles[0]);
-        while (alpha < 0.0) { alpha += 360.0; } // [0, 360)
-        double beta = Math.toDegrees(-rotationAngles[1]);
-        while (beta < -180.0) { beta += 360.0; } // [-180, 180)
-        double gamma = Math.toDegrees(rotationAngles[2]);
-        while (gamma < -90.0) { gamma += 360.0; } // [-90, 90)
-
-        maybeSendChange(alpha, beta, gamma);
-    }
-
-    private SensorManager getSensorManager() {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        if (mSensorManager == null) {
-            mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
-        }
-        return mSensorManager;
-    }
-
-    private boolean registerForAccelerometerSensor() {
-        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_ACCELEROMETER);
-        if (sensors.isEmpty()) {
-            return false;
-        }
-        // TODO: Consider handling multiple sensors.
-        return getSensorManager().registerListener(
-                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
-    }
-
-    private boolean registerForMagneticFieldSensor() {
-        List<Sensor> sensors = getSensorManager().getSensorList(Sensor.TYPE_MAGNETIC_FIELD);
-        if (sensors.isEmpty()) {
-            return false;
-        }
-        // TODO: Consider handling multiple sensors.
-        return getSensorManager().registerListener(
-                this, sensors.get(0), SensorManager.SENSOR_DELAY_FASTEST, mHandler);
-    }
-
-    private void unregisterFromSensors() {
-        getSensorManager().unregisterListener(this);
-    }
-
-    private void maybeSendChange(double alpha, double beta, double gamma) {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        if (mAlpha == null || mBeta == null || mGamma == null
-                || Math.abs(alpha - mAlpha) > DELTA_DEGRESS
-                || Math.abs(beta - mBeta) > DELTA_DEGRESS
-                || Math.abs(gamma - mGamma) > DELTA_DEGRESS) {
-            mAlpha = alpha;
-            mBeta = beta;
-            mGamma = gamma;
-            mManager.onOrientationChange(mAlpha, mBeta, mGamma);
-            // Now that we have successfully sent some data, reset whether we've sent an error.
-            mHaveSentErrorEvent = false;
-        }
-    }
-
-    /**
-     * SensorEventListener implementation.
-     * Callbacks happen on the thread on which we registered - the WebCore thread.
-     */
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        assert(event.values.length == 3);
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-
-        // We may get callbacks after the call to getSensorManager().unregisterListener() returns.
-        if (!mIsRunning) {
-            return;
-        }
-
-        switch (event.sensor.getType()) {
-          case Sensor.TYPE_ACCELEROMETER:
-            if (mGravityVector == null) {
-                mGravityVector = new float[3];
-            }
-            mGravityVector[0] = event.values[0];
-            mGravityVector[1] = event.values[1];
-            mGravityVector[2] = event.values[2];
-            getOrientationUsingGetRotationMatrix();
-            break;
-          case Sensor.TYPE_MAGNETIC_FIELD:
-            if (mMagneticFieldVector == null) {
-                mMagneticFieldVector = new float[3];
-            }
-            mMagneticFieldVector[0] = event.values[0];
-            mMagneticFieldVector[1] = event.values[1];
-            mMagneticFieldVector[2] = event.values[2];
-            getOrientationUsingGetRotationMatrix();
-            break;
-          default:
-            assert(false);
-        }
-    }
-
-    @Override
-    public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-    }
-}
diff --git a/core/java/android/webkit/GeolocationPermissionsClassic.java b/core/java/android/webkit/GeolocationPermissionsClassic.java
deleted file mode 100644
index 8a9df39..0000000
--- a/core/java/android/webkit/GeolocationPermissionsClassic.java
+++ /dev/null
@@ -1,234 +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;
-
-import android.os.Handler;
-import android.os.Message;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-// This class is the Java counterpart of the WebKit C++ GeolocationPermissions
-// class. It simply marshals calls from the UI thread to the WebKit thread.
-final class GeolocationPermissionsClassic extends GeolocationPermissions {
-    private Handler mHandler;
-    private Handler mUIHandler;
-
-    // A queue to store messages until the handler is ready.
-    private Vector<Message> mQueuedMessages;
-
-    // Message ids
-    static final int GET_ORIGINS = 0;
-    static final int GET_ALLOWED = 1;
-    static final int CLEAR = 2;
-    static final int ALLOW = 3;
-    static final int CLEAR_ALL = 4;
-
-    // Message ids on the UI thread
-    static final int RETURN_ORIGINS = 0;
-    static final int RETURN_ALLOWED = 1;
-
-    private static final String ORIGINS = "origins";
-    private static final String ORIGIN = "origin";
-    private static final String CALLBACK = "callback";
-    private static final String ALLOWED = "allowed";
-
-    // Global instance
-    private static GeolocationPermissionsClassic sInstance;
-
-    public static GeolocationPermissionsClassic getInstance() {
-        if (sInstance == null) {
-            sInstance = new GeolocationPermissionsClassic();
-        }
-        return sInstance;
-      }
-
-    /**
-     * Creates the UI message handler. Must be called on the UI thread.
-     * @hide
-     */
-    public void createUIHandler() {
-        if (mUIHandler == null) {
-            mUIHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    // Runs on the UI thread.
-                    switch (msg.what) {
-                        case RETURN_ORIGINS: {
-                            Map values = (Map) msg.obj;
-                            Set<String> origins = (Set<String>) values.get(ORIGINS);
-                            ValueCallback<Set<String> > callback = (ValueCallback<Set<String> >) values.get(CALLBACK);
-                            callback.onReceiveValue(origins);
-                        } break;
-                        case RETURN_ALLOWED: {
-                            Map values = (Map) msg.obj;
-                            Boolean allowed = (Boolean) values.get(ALLOWED);
-                            ValueCallback<Boolean> callback = (ValueCallback<Boolean>) values.get(CALLBACK);
-                            callback.onReceiveValue(allowed);
-                        } break;
-                    }
-                }
-            };
-        }
-    }
-
-    /**
-     * Creates the message handler. Must be called on the WebKit thread.
-     * @hide
-     */
-    public synchronized void createHandler() {
-        if (mHandler == null) {
-            mHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    // Runs on the WebKit thread.
-                    switch (msg.what) {
-                        case GET_ORIGINS: {
-                            Set origins = nativeGetOrigins();
-                            ValueCallback callback = (ValueCallback) msg.obj;
-                            Map values = new HashMap<String, Object>();
-                            values.put(CALLBACK, callback);
-                            values.put(ORIGINS, origins);
-                            postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
-                            } break;
-                        case GET_ALLOWED: {
-                            Map values = (Map) msg.obj;
-                            String origin = (String) values.get(ORIGIN);
-                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
-                            boolean allowed = nativeGetAllowed(origin);
-                            Map retValues = new HashMap<String, Object>();
-                            retValues.put(CALLBACK, callback);
-                            retValues.put(ALLOWED, Boolean.valueOf(allowed));
-                            postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues));
-                            } break;
-                        case CLEAR:
-                            nativeClear((String) msg.obj);
-                            break;
-                        case ALLOW:
-                            nativeAllow((String) msg.obj);
-                            break;
-                        case CLEAR_ALL:
-                            nativeClearAll();
-                            break;
-                    }
-                }
-            };
-
-            // Handle the queued messages
-            if (mQueuedMessages != null) {
-                while (!mQueuedMessages.isEmpty()) {
-                    mHandler.sendMessage(mQueuedMessages.remove(0));
-                }
-                mQueuedMessages = null;
-            }
-        }
-    }
-
-    /**
-     * Utility function to send a message to our handler.
-     */
-    private synchronized void postMessage(Message msg) {
-        if (mHandler == null) {
-            if (mQueuedMessages == null) {
-                mQueuedMessages = new Vector<Message>();
-            }
-            mQueuedMessages.add(msg);
-        } else {
-            mHandler.sendMessage(msg);
-        }
-    }
-
-    /**
-     * Utility function to send a message to the handler on the UI thread
-     */
-    private void postUIMessage(Message msg) {
-        if (mUIHandler != null) {
-            mUIHandler.sendMessage(msg);
-        }
-    }
-
-    // Note that we represent the origins as strings. These are created using
-    // WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
-    // (Database, Geolocation etc) do so, it's safe to match up origins based
-    // on this string.
-    @Override
-    public void getOrigins(ValueCallback<Set<String> > callback) {
-        if (callback != null) {
-            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-                Set origins = nativeGetOrigins();
-                callback.onReceiveValue(origins);
-            } else {
-                postMessage(Message.obtain(null, GET_ORIGINS, callback));
-            }
-        }
-    }
-
-    @Override
-    public void getAllowed(String origin, ValueCallback<Boolean> callback) {
-        if (callback == null) {
-            return;
-        }
-        if (origin == null) {
-            callback.onReceiveValue(null);
-            return;
-        }
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            boolean allowed = nativeGetAllowed(origin);
-            callback.onReceiveValue(Boolean.valueOf(allowed));
-        } else {
-            Map values = new HashMap<String, Object>();
-            values.put(ORIGIN, origin);
-            values.put(CALLBACK, callback);
-            postMessage(Message.obtain(null, GET_ALLOWED, values));
-        }
-    }
-
-    // This method may be called before the WebKit
-    // thread has intialized the message handler. Messages will be queued until
-    // this time.
-    @Override
-    public void clear(String origin) {
-        // Called on the UI thread.
-        postMessage(Message.obtain(null, CLEAR, origin));
-    }
-
-    // This method may be called before the WebKit
-    // thread has intialized the message handler. Messages will be queued until
-    // this time.
-    @Override
-    public void allow(String origin) {
-        // Called on the UI thread.
-        postMessage(Message.obtain(null, ALLOW, origin));
-    }
-
-    @Override
-    public void clearAll() {
-        // Called on the UI thread.
-        postMessage(Message.obtain(null, CLEAR_ALL));
-    }
-
-    GeolocationPermissionsClassic() {}
-
-    // Native functions, run on the WebKit thread.
-    private static native Set nativeGetOrigins();
-    private static native boolean nativeGetAllowed(String origin);
-    private static native void nativeClear(String origin);
-    private static native void nativeAllow(String origin);
-    private static native void nativeClearAll();
-}
diff --git a/core/java/android/webkit/GeolocationService.java b/core/java/android/webkit/GeolocationService.java
deleted file mode 100644
index 225053b..0000000
--- a/core/java/android/webkit/GeolocationService.java
+++ /dev/null
@@ -1,201 +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.
- */
-
-package android.webkit;
-
-import android.app.ActivityThread;
-import android.content.Context;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.os.Bundle;
-import android.util.Log;
-import android.webkit.WebViewCore;
-
-
-/**
- * Implements the Java side of GeolocationServiceAndroid.
- */
-final class GeolocationService implements LocationListener {
-
-    // Log tag
-    private static final String TAG = "geolocationService";
-
-    private long mNativeObject;
-    private LocationManager mLocationManager;
-    private boolean mIsGpsEnabled;
-    private boolean mIsRunning;
-    private boolean mIsNetworkProviderAvailable;
-    private boolean mIsGpsProviderAvailable;
-
-    /**
-     * Constructor
-     * @param context The context from which we obtain the system service.
-     * @param nativeObject The native object to which this object will report position updates and
-     *     errors.
-     */
-    public GeolocationService(Context context, long nativeObject) {
-        mNativeObject = nativeObject;
-        // Register newLocationAvailable with platform service.
-        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
-        if (mLocationManager == null) {
-            Log.e(TAG, "Could not get location manager.");
-        }
-     }
-
-    /**
-     * Start listening for location updates.
-     */
-    public boolean start() {
-        registerForLocationUpdates();
-        mIsRunning = true;
-        return mIsNetworkProviderAvailable || mIsGpsProviderAvailable;
-    }
-
-    /**
-     * Stop listening for location updates.
-     */
-    public void stop() {
-        unregisterFromLocationUpdates();
-        mIsRunning = false;
-    }
-
-    /**
-     * Sets whether to use the GPS.
-     * @param enable Whether to use the GPS.
-     */
-    public void setEnableGps(boolean enable) {
-        if (mIsGpsEnabled != enable) {
-            mIsGpsEnabled = enable;
-            if (mIsRunning) {
-                // There's no way to unregister from a single provider, so we can
-                // only unregister from all, then reregister with all but the GPS.
-                unregisterFromLocationUpdates();
-                registerForLocationUpdates();
-                // Check that the providers are still available after we re-register.
-                maybeReportError("The last location provider is no longer available");
-            }
-        }
-    }
-
-    /**
-     * LocationListener implementation.
-     * Called when the location has changed.
-     * @param location The new location, as a Location object.
-     */
-    public void onLocationChanged(Location location) {
-        // Callbacks from the system location sevice are queued to this thread, so it's possible
-        // that we receive callbacks after unregistering. At this point, the native object will no
-        // longer exist.
-        if (mIsRunning) {
-            nativeNewLocationAvailable(mNativeObject, location);
-        }
-    }
-
-    /**
-     * LocationListener implementation.
-     * Called when the provider status changes.
-     * @param provider The name of the provider.
-     * @param status The new status of the provider.
-     * @param extras an optional Bundle with provider specific data.
-     */
-    public void onStatusChanged(String providerName, int status, Bundle extras) {
-        boolean isAvailable = (status == LocationProvider.AVAILABLE);
-        if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
-            mIsNetworkProviderAvailable = isAvailable;
-        } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
-            mIsGpsProviderAvailable = isAvailable;
-        }
-        maybeReportError("The last location provider is no longer available");
-    }
-
-    /**
-     * LocationListener implementation.
-     * Called when the provider is enabled.
-     * @param provider The name of the location provider that is now enabled.
-     */
-    public void onProviderEnabled(String providerName) {
-        // No need to notify the native side. It's enough to start sending
-        // valid position fixes again.
-        if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
-            mIsNetworkProviderAvailable = true;
-        } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
-            mIsGpsProviderAvailable = true;
-        }
-    }
-
-    /**
-     * LocationListener implementation.
-     * Called when the provider is disabled.
-     * @param provider The name of the location provider that is now disabled.
-     */
-    public void onProviderDisabled(String providerName) {
-        if (LocationManager.NETWORK_PROVIDER.equals(providerName)) {
-            mIsNetworkProviderAvailable = false;
-        } else if (LocationManager.GPS_PROVIDER.equals(providerName)) {
-            mIsGpsProviderAvailable = false;
-        }
-        maybeReportError("The last location provider was disabled");
-    }
-
-    /**
-     * Registers this object with the location service.
-     */
-    private void registerForLocationUpdates() {
-        try {
-            // Registration may fail if providers are not present on the device.
-            try {
-                mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
-                mIsNetworkProviderAvailable = true;
-            } catch(IllegalArgumentException e) { }
-            if (mIsGpsEnabled) {
-                try {
-                    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
-                    mIsGpsProviderAvailable = true;
-                } catch(IllegalArgumentException e) { }
-            }
-        } catch(SecurityException e) {
-            Log.e(TAG, "Caught security exception registering for location updates from system. " +
-                "This should only happen in DumpRenderTree.");
-        }
-    }
-
-    /**
-     * Unregisters this object from the location service.
-     */
-    private void unregisterFromLocationUpdates() {
-        mLocationManager.removeUpdates(this);
-        mIsNetworkProviderAvailable = false;
-        mIsGpsProviderAvailable = false;
-    }
-
-    /**
-     * Reports an error if neither the network nor the GPS provider is available.
-     */
-    private void maybeReportError(String message) {
-        // Callbacks from the system location sevice are queued to this thread, so it's possible
-        // that we receive callbacks after unregistering. At this point, the native object will no
-        // longer exist.
-        if (mIsRunning && !mIsNetworkProviderAvailable && !mIsGpsProviderAvailable) {
-            nativeNewErrorAvailable(mNativeObject, message);
-        }
-    }
-
-    // Native functions
-    private static native void nativeNewLocationAvailable(long nativeObject, Location location);
-    private static native void nativeNewErrorAvailable(long nativeObject, String message);
-}
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
deleted file mode 100644
index 17eb2df..0000000
--- a/core/java/android/webkit/HTML5Audio.java
+++ /dev/null
@@ -1,358 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * HTML5 support class for Audio.
- *
- * This class runs almost entirely on the WebCore thread. The exception is when
- * accessing the WebView object to determine whether private browsing is
- * enabled.
- */
-class HTML5Audio extends Handler
-                 implements MediaPlayer.OnBufferingUpdateListener,
-                            MediaPlayer.OnCompletionListener,
-                            MediaPlayer.OnErrorListener,
-                            MediaPlayer.OnPreparedListener,
-                            MediaPlayer.OnSeekCompleteListener,
-                            AudioManager.OnAudioFocusChangeListener {
-    // Logging tag.
-    private static final String LOGTAG = "HTML5Audio";
-
-    private MediaPlayer mMediaPlayer;
-
-    // The C++ MediaPlayerPrivateAndroid object.
-    private int mNativePointer;
-    // The private status of the view that created this player
-    private IsPrivateBrowsingEnabledGetter mIsPrivateBrowsingEnabledGetter;
-
-    private static int IDLE                =  0;
-    private static int INITIALIZED         =  1;
-    private static int PREPARED            =  2;
-    private static int STARTED             =  4;
-    private static int COMPLETE            =  5;
-    private static int PAUSED              =  6;
-    private static int PAUSED_TRANSITORILY =  7;
-    private static int STOPPED             = -2;
-    private static int ERROR               = -1;
-
-    private int mState = IDLE;
-
-    private String mUrl;
-    private boolean mAskToPlay = false;
-    private boolean mLoopEnabled = false;
-    private boolean mProcessingOnEnd = false;
-    private Context mContext;
-
-    // Timer thread -> UI thread
-    private static final int TIMEUPDATE = 100;
-
-    private static final String COOKIE = "Cookie";
-    private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
-
-    // The spec says the timer should fire every 250 ms or less.
-    private static final int TIMEUPDATE_PERIOD = 250;  // ms
-    // The timer for timeupate events.
-    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
-    private Timer mTimer;
-    private final class TimeupdateTask extends TimerTask {
-        @Override
-        public void run() {
-            HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
-        }
-    }
-
-    // Helper class to determine whether private browsing is enabled in the
-    // given WebView. Queries the WebView on the UI thread. Calls to get()
-    // block until the data is available.
-    private class IsPrivateBrowsingEnabledGetter {
-        private boolean mIsReady;
-        private boolean mIsPrivateBrowsingEnabled;
-        IsPrivateBrowsingEnabledGetter(Looper uiThreadLooper, final WebViewClassic webView) {
-            new Handler(uiThreadLooper).post(new Runnable() {
-                @Override
-                public void run() {
-                    synchronized(IsPrivateBrowsingEnabledGetter.this) {
-                        mIsPrivateBrowsingEnabled = webView.isPrivateBrowsingEnabled();
-                        mIsReady = true;
-                        IsPrivateBrowsingEnabledGetter.this.notify();
-                    }
-                }
-            });
-        }
-        synchronized boolean get() {
-            while (!mIsReady) {
-                try {
-                    wait();
-                } catch (InterruptedException e) {
-                }
-            }
-            return mIsPrivateBrowsingEnabled;
-        }
-    };
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case TIMEUPDATE: {
-                try {
-                    if (mState != ERROR && mMediaPlayer.isPlaying()) {
-                        int position = mMediaPlayer.getCurrentPosition();
-                        nativeOnTimeupdate(position, mNativePointer);
-                    }
-                } catch (IllegalStateException e) {
-                    mState = ERROR;
-                }
-            }
-        }
-    }
-
-    // event listeners for MediaPlayer
-    // Those are called from the same thread we created the MediaPlayer
-    // (i.e. the webviewcore thread here)
-
-    // MediaPlayer.OnBufferingUpdateListener
-    @Override
-    public void onBufferingUpdate(MediaPlayer mp, int percent) {
-        nativeOnBuffering(percent, mNativePointer);
-    }
-
-    // MediaPlayer.OnCompletionListener;
-    @Override
-    public void onCompletion(MediaPlayer mp) {
-        mState = COMPLETE;
-        mProcessingOnEnd = true;
-        nativeOnEnded(mNativePointer);
-        mProcessingOnEnd = false;
-        if (mLoopEnabled == true) {
-            nativeOnRequestPlay(mNativePointer);
-            mLoopEnabled = false;
-        }
-    }
-
-    // MediaPlayer.OnErrorListener
-    @Override
-    public boolean onError(MediaPlayer mp, int what, int extra) {
-        mState = ERROR;
-        resetMediaPlayer();
-        mState = IDLE;
-        return false;
-    }
-
-    // MediaPlayer.OnPreparedListener
-    @Override
-    public void onPrepared(MediaPlayer mp) {
-        mState = PREPARED;
-        if (mTimer != null) {
-            mTimer.schedule(new TimeupdateTask(),
-                            TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
-        }
-        nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
-        if (mAskToPlay) {
-            mAskToPlay = false;
-            play();
-        }
-    }
-
-    // MediaPlayer.OnSeekCompleteListener
-    @Override
-    public void onSeekComplete(MediaPlayer mp) {
-        nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
-    }
-
-
-    /**
-     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
-     */
-    public HTML5Audio(WebViewCore webViewCore, int nativePtr) {
-        // Save the native ptr
-        mNativePointer = nativePtr;
-        resetMediaPlayer();
-        mContext = webViewCore.getContext();
-        mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
-                webViewCore.getContext().getMainLooper(), webViewCore.getWebViewClassic());
-    }
-
-    private void resetMediaPlayer() {
-        if (mMediaPlayer == null) {
-            mMediaPlayer = new MediaPlayer();
-        } else {
-            mMediaPlayer.reset();
-        }
-        mMediaPlayer.setOnBufferingUpdateListener(this);
-        mMediaPlayer.setOnCompletionListener(this);
-        mMediaPlayer.setOnErrorListener(this);
-        mMediaPlayer.setOnPreparedListener(this);
-        mMediaPlayer.setOnSeekCompleteListener(this);
-
-        if (mTimer != null) {
-            mTimer.cancel();
-        }
-        mTimer = new Timer();
-        mState = IDLE;
-    }
-
-    private void setDataSource(String url) {
-        mUrl = url;
-        try {
-            if (mState != IDLE) {
-                resetMediaPlayer();
-            }
-            String cookieValue = CookieManager.getInstance().getCookie(
-                    url, mIsPrivateBrowsingEnabledGetter.get());
-            Map<String, String> headers = new HashMap<String, String>();
-
-            if (cookieValue != null) {
-                headers.put(COOKIE, cookieValue);
-            }
-            if (mIsPrivateBrowsingEnabledGetter.get()) {
-                headers.put(HIDE_URL_LOGS, "true");
-            }
-
-            mMediaPlayer.setDataSource(mContext, Uri.parse(url), headers);
-            mState = INITIALIZED;
-            mMediaPlayer.prepareAsync();
-        } catch (IOException e) {
-            String debugUrl = url.length() > 128 ? url.substring(0, 128) + "..." : url;
-            Log.e(LOGTAG, "couldn't load the resource: "+ debugUrl +" exc: " + e);
-            resetMediaPlayer();
-        }
-    }
-
-    @Override
-    public void onAudioFocusChange(int focusChange) {
-        switch (focusChange) {
-        case AudioManager.AUDIOFOCUS_GAIN:
-            // resume playback
-            if (mMediaPlayer == null) {
-                resetMediaPlayer();
-            } else if (mState == PAUSED_TRANSITORILY && !mMediaPlayer.isPlaying()) {
-                mMediaPlayer.start();
-                mState = STARTED;
-            }
-            break;
-
-        case AudioManager.AUDIOFOCUS_LOSS:
-            // Lost focus for an unbounded amount of time: stop playback.
-            if (mState != ERROR && mMediaPlayer.isPlaying()) {
-                mMediaPlayer.stop();
-                mState = STOPPED;
-            }
-            break;
-
-        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-            // Lost focus for a short time, but we have to stop
-            // playback.
-            if (mState != ERROR && mMediaPlayer.isPlaying()) {
-                pause(PAUSED_TRANSITORILY);
-            }
-            break;
-        }
-    }
-
-
-    private void play() {
-        if (mState == COMPLETE && mLoopEnabled == true) {
-            // Play it again, Sam
-            mMediaPlayer.start();
-            mState = STARTED;
-            return;
-        }
-
-        if (((mState >= ERROR && mState < PREPARED)) && mUrl != null) {
-            resetMediaPlayer();
-            setDataSource(mUrl);
-            mAskToPlay = true;
-        }
-
-        if (mState >= PREPARED) {
-            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-            int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN);
-
-            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-                mMediaPlayer.start();
-                mState = STARTED;
-            }
-        }
-    }
-
-    private void pause() {
-        pause(PAUSED);
-    }
-
-    private void pause(int state) {
-        if (mState == STARTED) {
-            if (mTimer != null) {
-                mTimer.purge();
-            }
-            mMediaPlayer.pause();
-            mState = state;
-        }
-    }
-
-    private void seek(int msec) {
-        if (mProcessingOnEnd == true && mState == COMPLETE && msec == 0) {
-            mLoopEnabled = true;
-        }
-        if (mState >= PREPARED) {
-            mMediaPlayer.seekTo(msec);
-        }
-    }
-
-    /**
-     * Called only over JNI when WebKit is happy to
-     * destroy the media player.
-     */
-    private void teardown() {
-        mMediaPlayer.release();
-        mMediaPlayer = null;
-        mState = ERROR;
-        mNativePointer = 0;
-    }
-
-    private float getMaxTimeSeekable() {
-        if (mState >= PREPARED) {
-            return mMediaPlayer.getDuration() / 1000.0f;
-        } else {
-            return 0;
-        }
-    }
-
-    private native void nativeOnBuffering(int percent, int nativePointer);
-    private native void nativeOnEnded(int nativePointer);
-    private native void nativeOnRequestPlay(int nativePointer);
-    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
-    private native void nativeOnTimeupdate(int position, int nativePointer);
-
-}
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
deleted file mode 100644
index 6fb32c8..0000000
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ /dev/null
@@ -1,421 +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;
-
-import android.content.Context;
-import android.media.MediaPlayer;
-import android.media.Metadata;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.MediaController;
-import android.widget.MediaController.MediaPlayerControl;
-
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoFullScreen extends HTML5VideoView
-    implements MediaPlayerControl, MediaPlayer.OnPreparedListener,
-    View.OnTouchListener {
-
-    // Add this sub-class to handle the resizing when rotating screen.
-    private class VideoSurfaceView extends SurfaceView {
-
-        public VideoSurfaceView(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
-            int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
-            if (mVideoWidth > 0 && mVideoHeight > 0) {
-                if ( mVideoWidth * height  > width * mVideoHeight ) {
-                    height = width * mVideoHeight / mVideoWidth;
-                } else if ( mVideoWidth * height  < width * mVideoHeight ) {
-                    width = height * mVideoWidth / mVideoHeight;
-                }
-            }
-            setMeasuredDimension(width, height);
-        }
-    }
-
-    // This view will contain the video.
-    private VideoSurfaceView mVideoSurfaceView;
-
-    // We need the full screen state to decide which surface to render to and
-    // when to create the MediaPlayer accordingly.
-    static final int FULLSCREEN_OFF               = 0;
-    static final int FULLSCREEN_SURFACECREATING   = 1;
-    static final int FULLSCREEN_SURFACECREATED    = 2;
-
-    private int mFullScreenMode;
-    // The Media Controller only used for full screen mode
-    private MediaController mMediaController;
-
-    // SurfaceHolder for full screen
-    private SurfaceHolder mSurfaceHolder = null;
-
-    // Data only for MediaController
-    private boolean mCanSeekBack;
-    private boolean mCanSeekForward;
-    private boolean mCanPause;
-    private int mCurrentBufferPercentage;
-
-    // The progress view.
-    private static View mProgressView;
-    // The container for the progress view and video view
-    private static FrameLayout mLayout;
-
-    // The video size will be ready when prepared. Used to make sure the aspect
-    // ratio is correct.
-    private int mVideoWidth;
-    private int mVideoHeight;
-    private boolean mPlayingWhenDestroyed = false;
-    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
-    {
-        @Override
-        public void surfaceChanged(SurfaceHolder holder, int format,
-                                    int w, int h)
-        {
-            if (mPlayer != null && mMediaController != null
-                    && mCurrentState == STATE_PREPARED) {
-                if (mMediaController.isShowing()) {
-                    // ensure the controller will get repositioned later
-                    mMediaController.hide();
-                }
-                mMediaController.show();
-            }
-        }
-
-        @Override
-        public void surfaceCreated(SurfaceHolder holder)
-        {
-            mSurfaceHolder = holder;
-            mFullScreenMode = FULLSCREEN_SURFACECREATED;
-
-            prepareForFullScreen();
-        }
-
-        @Override
-        public void surfaceDestroyed(SurfaceHolder holder)
-        {
-            mPlayingWhenDestroyed = mPlayer.isPlaying();
-            pauseAndDispatch(mProxy);
-            // We need to set the display to null before switching into inline
-            // mode to avoid error.
-            mPlayer.setDisplay(null);
-            mSurfaceHolder = null;
-            if (mMediaController != null) {
-                mMediaController.hide();
-            }
-        }
-    };
-
-    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
-        new MediaPlayer.OnVideoSizeChangedListener() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
-                mVideoWidth = mp.getVideoWidth();
-                mVideoHeight = mp.getVideoHeight();
-                if (mVideoWidth != 0 && mVideoHeight != 0) {
-                    mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
-                }
-            }
-    };
-
-    private SurfaceView getSurfaceView() {
-        return mVideoSurfaceView;
-    }
-
-    HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) {
-        mVideoSurfaceView = new VideoSurfaceView(context);
-        mFullScreenMode = FULLSCREEN_OFF;
-        mVideoWidth = 0;
-        mVideoHeight = 0;
-        init(videoLayerId, position, skipPrepare);
-    }
-
-    private void setMediaController(MediaController m) {
-        mMediaController  = m;
-        attachMediaController();
-    }
-
-    private void attachMediaController() {
-        if (mPlayer != null && mMediaController != null) {
-            mMediaController.setMediaPlayer(this);
-            mMediaController.setAnchorView(mVideoSurfaceView);
-            //Will be enabled when prepared
-            mMediaController.setEnabled(false);
-        }
-    }
-
-    @Override
-    public void decideDisplayMode() {
-        mPlayer.setDisplay(mSurfaceHolder);
-    }
-
-    private void prepareForFullScreen() {
-        MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout);
-        mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
-        setMediaController(mc);
-        mPlayer.setScreenOnWhilePlaying(true);
-        mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
-        prepareDataAndDisplayMode(mProxy);
-    }
-
-
-    private void toggleMediaControlsVisiblity() {
-        if (mMediaController.isShowing()) {
-            mMediaController.hide();
-        } else {
-            mMediaController.show();
-        }
-    }
-
-    @Override
-    public void onPrepared(MediaPlayer mp) {
-        super.onPrepared(mp);
-
-        mVideoSurfaceView.setOnTouchListener(this);
-        // Get the capabilities of the player for this stream
-        Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
-                MediaPlayer.BYPASS_METADATA_FILTER);
-        if (data != null) {
-            mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
-                    || data.getBoolean(Metadata.PAUSE_AVAILABLE);
-            mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
-                    || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
-            mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
-                    || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
-        } else {
-            mCanPause = mCanSeekBack = mCanSeekForward = true;
-        }
-
-        if (getStartWhenPrepared()) {
-            mPlayer.start();
-            // Clear the flag.
-            setStartWhenPrepared(false);
-        }
-
-        // mMediaController status depends on the Metadata result, so put it
-        // after reading the MetaData.
-        // And make sure mPlayer state is updated before showing the controller.
-        if (mMediaController != null) {
-            mMediaController.setEnabled(true);
-            mMediaController.show();
-        }
-
-        if (mProgressView != null) {
-            mProgressView.setVisibility(View.GONE);
-        }
-
-        mVideoWidth = mp.getVideoWidth();
-        mVideoHeight = mp.getVideoHeight();
-        // This will trigger the onMeasure to get the display size right.
-        mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
-
-    }
-
-    @Override
-    public boolean fullScreenExited() {
-        return (mLayout == null);
-    }
-
-    private final WebChromeClient.CustomViewCallback mCallback =
-        new WebChromeClient.CustomViewCallback() {
-            @Override
-            public void onCustomViewHidden() {
-                // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
-                // which happens when the video view is detached from its parent
-                // view. This happens in the WebChromeClient before this method
-                // is invoked.
-                mLayout.removeView(getSurfaceView());
-
-                if (mProgressView != null) {
-                    mLayout.removeView(mProgressView);
-                    mProgressView = null;
-                }
-                mLayout = null;
-                // Re enable plugin views.
-                mProxy.getWebView().getViewManager().showAll();
-                // Don't show the controller after exiting the full screen.
-                mMediaController = null;
-                // Continue the inline mode playing if necessary.
-                mProxy.dispatchOnStopFullScreen(mPlayingWhenDestroyed);
-                mProxy = null;
-            }
-        };
-
-    @Override
-    public void enterFullScreenVideoState(int layerId,
-            HTML5VideoViewProxy proxy, WebViewClassic webView) {
-        mFullScreenMode = FULLSCREEN_SURFACECREATING;
-        mCurrentBufferPercentage = 0;
-        mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
-        mProxy = proxy;
-
-        mVideoSurfaceView.getHolder().addCallback(mSHCallback);
-        mVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
-        mVideoSurfaceView.setFocusable(true);
-        mVideoSurfaceView.setFocusableInTouchMode(true);
-        mVideoSurfaceView.requestFocus();
-        mVideoSurfaceView.setOnKeyListener(mProxy);
-        // Create a FrameLayout that will contain the VideoView and the
-        // progress view (if any).
-        mLayout = new FrameLayout(mProxy.getContext());
-        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
-                            ViewGroup.LayoutParams.WRAP_CONTENT,
-                            ViewGroup.LayoutParams.WRAP_CONTENT,
-                            Gravity.CENTER);
-
-        mLayout.addView(getSurfaceView(), layoutParams);
-
-        mLayout.setVisibility(View.VISIBLE);
-        WebChromeClient client = webView.getWebChromeClient();
-        if (client != null) {
-            if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onShowCustomView");
-            client.onShowCustomView(mLayout, mCallback);
-            // Plugins like Flash will draw over the video so hide
-            // them while we're playing.
-            if (webView.getViewManager() != null)
-                webView.getViewManager().hideAll();
-
-            if (DebugFlags.TRACE_CALLBACK) {
-                Log.d(CallbackProxy.LOGTAG, "getVideoLoadingProgressView");
-            }
-            mProgressView = client.getVideoLoadingProgressView();
-            if (mProgressView != null) {
-                mLayout.addView(mProgressView, layoutParams);
-                mProgressView.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
-    /**
-     * @return true when we are in full screen mode, even the surface not fully
-     * created.
-     */
-    @Override
-    public boolean isFullScreenMode() {
-        return true;
-    }
-
-    // MediaController FUNCTIONS:
-    @Override
-    public boolean canPause() {
-        return mCanPause;
-    }
-
-    @Override
-    public boolean canSeekBackward() {
-        return mCanSeekBack;
-    }
-
-    @Override
-    public boolean canSeekForward() {
-        return mCanSeekForward;
-    }
-
-    @Override
-    public int getBufferPercentage() {
-        if (mPlayer != null) {
-            return mCurrentBufferPercentage;
-        }
-    return 0;
-    }
-
-    @Override
-    public int getAudioSessionId() {
-        if (mPlayer == null) {
-            return 0;
-        }
-        return mPlayer.getAudioSessionId();
-    }
-
-    @Override
-    public void showControllerInFullScreen() {
-        if (mMediaController != null) {
-            mMediaController.show(0);
-        }
-    }
-
-    // Other listeners functions:
-    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
-        new MediaPlayer.OnBufferingUpdateListener() {
-        @Override
-        public void onBufferingUpdate(MediaPlayer mp, int percent) {
-            mCurrentBufferPercentage = percent;
-        }
-    };
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        if (mFullScreenMode >= FULLSCREEN_SURFACECREATED
-                && mMediaController != null) {
-            toggleMediaControlsVisiblity();
-        }
-        return false;
-    }
-
-    @Override
-    protected void switchProgressView(boolean playerBuffering) {
-        if (mProgressView != null) {
-            if (playerBuffering) {
-                mProgressView.setVisibility(View.VISIBLE);
-            } else {
-                mProgressView.setVisibility(View.GONE);
-            }
-        }
-        return;
-    }
-
-    static class FullScreenMediaController extends MediaController {
-
-        View mVideoView;
-
-        public FullScreenMediaController(Context context, View video) {
-            super(context);
-            mVideoView = video;
-        }
-
-        @Override
-        public void show() {
-            super.show();
-            if (mVideoView != null) {
-                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
-            }
-        }
-
-        @Override
-        public void hide() {
-            if (mVideoView != null) {
-                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
-                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
-            }
-            super.hide();
-        }
-
-    }
-
-}
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
deleted file mode 100644
index 2ab2ab9..0000000
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ /dev/null
@@ -1,132 +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;
-
-import android.Manifest.permission;
-import android.content.pm.PackageManager;
-import android.graphics.SurfaceTexture;
-import android.webkit.HTML5VideoView;
-import android.webkit.HTML5VideoViewProxy;
-import android.view.Surface;
-import android.opengl.GLES20;
-import android.os.PowerManager;
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoInline extends HTML5VideoView{
-
-    // Due to the fact that the decoder consume a lot of memory, we make the
-    // surface texture as singleton. But the GL texture (m_textureNames)
-    // associated with the surface texture can be used for showing the screen
-    // shot when paused, so they are not singleton.
-    private static SurfaceTexture mSurfaceTexture = null;
-    private static int[] mTextureNames = null;
-    // Every time when the VideoLayer Id change, we need to recreate the
-    // SurfaceTexture in order to delete the old video's decoder memory.
-    private static int mVideoLayerUsingSurfaceTexture = -1;
-
-    // Video control FUNCTIONS:
-    @Override
-    public void start() {
-        if (!getPauseDuringPreparing()) {
-            super.start();
-        }
-    }
-
-    HTML5VideoInline(int videoLayerId, int position, boolean skipPrepare) {
-        init(videoLayerId, position, skipPrepare);
-    }
-
-    @Override
-    public void decideDisplayMode() {
-        SurfaceTexture surfaceTexture = getSurfaceTexture(getVideoLayerId());
-        Surface surface = new Surface(surfaceTexture);
-        mPlayer.setSurface(surface);
-        surface.release();
-    }
-
-    // Normally called immediately after setVideoURI. But for full screen,
-    // this should be after surface holder created
-    @Override
-    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
-        super.prepareDataAndDisplayMode(proxy);
-        setFrameAvailableListener(proxy);
-        // TODO: This is a workaround, after b/5375681 fixed, we should switch
-        // to the better way.
-        if (mProxy.getContext().checkCallingOrSelfPermission(permission.WAKE_LOCK)
-                == PackageManager.PERMISSION_GRANTED) {
-            mPlayer.setWakeMode(proxy.getContext(), PowerManager.FULL_WAKE_LOCK);
-        }
-    }
-
-    // Pause the play and update the play/pause button
-    @Override
-    public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
-        super.pauseAndDispatch(proxy);
-    }
-
-    // Inline Video specific FUNCTIONS:
-
-    public static SurfaceTexture getSurfaceTexture(int videoLayerId) {
-        // Create the surface texture.
-        if (videoLayerId != mVideoLayerUsingSurfaceTexture
-            || mSurfaceTexture == null
-            || mTextureNames == null) {
-            // The GL texture will store in the VideoLayerManager at native side.
-            // They will be clean up when requested.
-            // The reason we recreated GL texture name is for screen shot support.
-            mTextureNames = new int[1];
-            GLES20.glGenTextures(1, mTextureNames, 0);
-            mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
-        }
-        mVideoLayerUsingSurfaceTexture = videoLayerId;
-        return mSurfaceTexture;
-    }
-
-    public static boolean surfaceTextureDeleted() {
-        return (mSurfaceTexture == null);
-    }
-
-    @Override
-    public void deleteSurfaceTexture() {
-        cleanupSurfaceTexture();
-        return;
-    }
-
-    public static void cleanupSurfaceTexture() {
-        mSurfaceTexture = null;
-        mVideoLayerUsingSurfaceTexture = -1;
-        return;
-    }
-
-    @Override
-    public int getTextureName() {
-        if (mTextureNames != null) {
-            return mTextureNames[0];
-        } else {
-            return 0;
-        }
-    }
-
-    private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
-        if (mSurfaceTexture != null) {
-            mSurfaceTexture.setOnFrameAvailableListener(l);
-        }
-    }
-
-}
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
deleted file mode 100644
index 0e8a5db..0000000
--- a/core/java/android/webkit/HTML5VideoView.java
+++ /dev/null
@@ -1,383 +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;
-
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.webkit.HTML5VideoViewProxy;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * @hide This is only used by the browser
- */
-public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
-
-    protected static final String LOGTAG = "HTML5VideoView";
-
-    protected static final String COOKIE = "Cookie";
-    protected static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
-
-    // For handling the seekTo before prepared, we need to know whether or not
-    // the video is prepared. Therefore, we differentiate the state between
-    // prepared and not prepared.
-    // When the video is not prepared, we will have to save the seekTo time,
-    // and use it when prepared to play.
-    // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side.
-    // Please keep them in sync when changed.
-    static final int STATE_INITIALIZED        = 0;
-    static final int STATE_PREPARING          = 1;
-    static final int STATE_PREPARED           = 2;
-    static final int STATE_PLAYING            = 3;
-    static final int STATE_RESETTED           = 4;
-    static final int STATE_RELEASED           = 5;
-
-    protected HTML5VideoViewProxy mProxy;
-
-    // Save the seek time when not prepared. This can happen when switching
-    // video besides initial load.
-    protected int mSaveSeekTime;
-
-    // This is used to find the VideoLayer on the native side.
-    protected int mVideoLayerId;
-
-    // Given the fact we only have one SurfaceTexture, we cannot support multiple
-    // player at the same time. We may recreate a new one and abandon the old
-    // one at transition time.
-    protected static MediaPlayer mPlayer = null;
-    protected static int mCurrentState = -1;
-
-    // We need to save such info.
-    protected Uri mUri;
-    protected Map<String, String> mHeaders;
-
-    // The timer for timeupate events.
-    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
-    protected static Timer mTimer;
-
-    protected boolean mPauseDuringPreparing;
-
-    // The spec says the timer should fire every 250 ms or less.
-    private static final int TIMEUPDATE_PERIOD = 250;  // ms
-    private boolean mSkipPrepare = false;
-
-    // common Video control FUNCTIONS:
-    public void start() {
-        if (mCurrentState == STATE_PREPARED) {
-            // When replaying the same video, there is no onPrepared call.
-            // Therefore, the timer should be set up here.
-            if (mTimer == null)
-            {
-                mTimer = new Timer();
-                mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD,
-                        TIMEUPDATE_PERIOD);
-            }
-            mPlayer.start();
-            setPlayerBuffering(false);
-        }
-    }
-
-    public void pause() {
-        if (isPlaying()) {
-            mPlayer.pause();
-        } else if (mCurrentState == STATE_PREPARING) {
-            mPauseDuringPreparing = true;
-        }
-        // Delete the Timer to stop it since there is no stop call.
-        if (mTimer != null) {
-            mTimer.purge();
-            mTimer.cancel();
-            mTimer = null;
-        }
-    }
-
-    public int getDuration() {
-        if (mCurrentState == STATE_PREPARED) {
-            return mPlayer.getDuration();
-        } else {
-            return -1;
-        }
-    }
-
-    public int getCurrentPosition() {
-        if (mCurrentState == STATE_PREPARED) {
-            return mPlayer.getCurrentPosition();
-        }
-        return 0;
-    }
-
-    public void seekTo(int pos) {
-        if (mCurrentState == STATE_PREPARED)
-            mPlayer.seekTo(pos);
-        else
-            mSaveSeekTime = pos;
-    }
-
-    public boolean isPlaying() {
-        if (mCurrentState == STATE_PREPARED) {
-            return mPlayer.isPlaying();
-        } else {
-            return false;
-        }
-    }
-
-    public void reset() {
-        if (mCurrentState < STATE_RESETTED) {
-            mPlayer.reset();
-        }
-        mCurrentState = STATE_RESETTED;
-    }
-
-    public void stopPlayback() {
-        if (mCurrentState == STATE_PREPARED) {
-            mPlayer.stop();
-        }
-    }
-
-    public static void release() {
-        if (mPlayer != null && mCurrentState != STATE_RELEASED) {
-            mPlayer.release();
-            mPlayer = null;
-        }
-        mCurrentState = STATE_RELEASED;
-    }
-
-    public boolean isReleased() {
-        return mCurrentState == STATE_RELEASED;
-    }
-
-    public boolean getPauseDuringPreparing() {
-        return mPauseDuringPreparing;
-    }
-
-    // Every time we start a new Video, we create a VideoView and a MediaPlayer
-    public void init(int videoLayerId, int position, boolean skipPrepare) {
-        if (mPlayer == null) {
-            mPlayer = new MediaPlayer();
-            mCurrentState = STATE_INITIALIZED;
-        }
-        mSkipPrepare = skipPrepare;
-        // If we want to skip the prepare, then we keep the state.
-        if (!mSkipPrepare) {
-            mCurrentState = STATE_INITIALIZED;
-        }
-        mProxy = null;
-        mVideoLayerId = videoLayerId;
-        mSaveSeekTime = position;
-        mTimer = null;
-        mPauseDuringPreparing = false;
-    }
-
-    protected HTML5VideoView() {
-    }
-
-    protected static Map<String, String> generateHeaders(String url,
-            HTML5VideoViewProxy proxy) {
-        boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled();
-        String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate);
-        Map<String, String> headers = new HashMap<String, String>();
-        if (cookieValue != null) {
-            headers.put(COOKIE, cookieValue);
-        }
-        if (isPrivate) {
-            headers.put(HIDE_URL_LOGS, "true");
-        }
-
-        return headers;
-    }
-
-    public void setVideoURI(String uri, HTML5VideoViewProxy proxy) {
-        // When switching players, surface texture will be reused.
-        mUri = Uri.parse(uri);
-        mHeaders = generateHeaders(uri, proxy);
-    }
-
-    // Listeners setup FUNCTIONS:
-    public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
-        mPlayer.setOnCompletionListener(proxy);
-    }
-
-    public void setOnErrorListener(HTML5VideoViewProxy proxy) {
-        mPlayer.setOnErrorListener(proxy);
-    }
-
-    public void setOnPreparedListener(HTML5VideoViewProxy proxy) {
-        mProxy = proxy;
-        mPlayer.setOnPreparedListener(this);
-    }
-
-    public void setOnInfoListener(HTML5VideoViewProxy proxy) {
-        mPlayer.setOnInfoListener(proxy);
-    }
-
-    public void prepareDataCommon(HTML5VideoViewProxy proxy) {
-        if (!mSkipPrepare) {
-            try {
-                mPlayer.reset();
-                mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders);
-                mPlayer.prepareAsync();
-            } catch (IllegalArgumentException e) {
-                e.printStackTrace();
-            } catch (IllegalStateException e) {
-                e.printStackTrace();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-            mCurrentState = STATE_PREPARING;
-        } else {
-            // If we skip prepare and the onPrepared happened in inline mode, we
-            // don't need to call prepare again, we just need to call onPrepared
-            // to refresh the state here.
-            if (mCurrentState >= STATE_PREPARED) {
-                onPrepared(mPlayer);
-            }
-            mSkipPrepare = false;
-        }
-    }
-
-    public void reprepareData(HTML5VideoViewProxy proxy) {
-        mPlayer.reset();
-        prepareDataCommon(proxy);
-    }
-
-    // Normally called immediately after setVideoURI. But for full screen,
-    // this should be after surface holder created
-    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
-        // SurfaceTexture will be created lazily here for inline mode
-        decideDisplayMode();
-
-        setOnCompletionListener(proxy);
-        setOnPreparedListener(proxy);
-        setOnErrorListener(proxy);
-        setOnInfoListener(proxy);
-
-        prepareDataCommon(proxy);
-    }
-
-
-    // Common code
-    public int getVideoLayerId() {
-        return mVideoLayerId;
-    }
-
-
-    public int getCurrentState() {
-        if (isPlaying()) {
-            return STATE_PLAYING;
-        } else {
-            return mCurrentState;
-        }
-    }
-
-    private static final class TimeupdateTask extends TimerTask {
-        private HTML5VideoViewProxy mProxy;
-
-        public TimeupdateTask(HTML5VideoViewProxy proxy) {
-            mProxy = proxy;
-        }
-
-        @Override
-        public void run() {
-            mProxy.onTimeupdate();
-        }
-    }
-
-    @Override
-    public void onPrepared(MediaPlayer mp) {
-        mCurrentState = STATE_PREPARED;
-        seekTo(mSaveSeekTime);
-        if (mProxy != null) {
-            mProxy.onPrepared(mp);
-        }
-        if (mPauseDuringPreparing) {
-            pauseAndDispatch(mProxy);
-            mPauseDuringPreparing = false;
-        }
-    }
-
-    // Pause the play and update the play/pause button
-    public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
-        pause();
-        if (proxy != null) {
-            proxy.dispatchOnPaused();
-        }
-    }
-
-    // Below are functions that are different implementation on inline and full-
-    // screen mode. Some are specific to one type, but currently are called
-    // directly from the proxy.
-    public void enterFullScreenVideoState(int layerId,
-            HTML5VideoViewProxy proxy, WebViewClassic webView) {
-    }
-
-    public boolean isFullScreenMode() {
-        return false;
-    }
-
-    public void decideDisplayMode() {
-    }
-
-    public boolean getReadyToUseSurfTex() {
-        return false;
-    }
-
-    public void deleteSurfaceTexture() {
-    }
-
-    public int getTextureName() {
-        return 0;
-    }
-
-    // This is true only when the player is buffering and paused
-    public boolean mPlayerBuffering = false;
-
-    public boolean getPlayerBuffering() {
-        return mPlayerBuffering;
-    }
-
-    public void setPlayerBuffering(boolean playerBuffering) {
-        mPlayerBuffering = playerBuffering;
-        switchProgressView(playerBuffering);
-    }
-
-
-    protected void switchProgressView(boolean playerBuffering) {
-        // Only used in HTML5VideoFullScreen
-    }
-
-    public boolean fullScreenExited() {
-        // Only meaningful for HTML5VideoFullScreen
-        return false;
-    }
-
-    private boolean mStartWhenPrepared = false;
-
-    public void setStartWhenPrepared(boolean willPlay) {
-        mStartWhenPrepared  = willPlay;
-    }
-
-    public boolean getStartWhenPrepared() {
-        return mStartWhenPrepared;
-    }
-
-    public void showControllerInFullScreen() {
-    }
-
-}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
deleted file mode 100644
index e8538f6..0000000
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ /dev/null
@@ -1,825 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
-import android.net.http.EventHandler;
-import android.net.http.Headers;
-import android.net.http.RequestHandle;
-import android.net.http.RequestQueue;
-import android.net.http.SslCertificate;
-import android.net.http.SslError;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * <p>Proxy for HTML5 video views.
- */
-class HTML5VideoViewProxy extends Handler
-                          implements MediaPlayer.OnPreparedListener,
-                          MediaPlayer.OnCompletionListener,
-                          MediaPlayer.OnErrorListener,
-                          MediaPlayer.OnInfoListener,
-                          SurfaceTexture.OnFrameAvailableListener,
-                          View.OnKeyListener {
-    // Logging tag.
-    private static final String LOGTAG = "HTML5VideoViewProxy";
-
-    // Message Ids for WebCore thread -> UI thread communication.
-    private static final int PLAY                = 100;
-    private static final int SEEK                = 101;
-    private static final int PAUSE               = 102;
-    private static final int ERROR               = 103;
-    private static final int LOAD_DEFAULT_POSTER = 104;
-    private static final int BUFFERING_START     = 105;
-    private static final int BUFFERING_END       = 106;
-    private static final int ENTER_FULLSCREEN    = 107;
-
-    // Message Ids to be handled on the WebCore thread
-    private static final int PREPARED          = 200;
-    private static final int ENDED             = 201;
-    private static final int POSTER_FETCHED    = 202;
-    private static final int PAUSED            = 203;
-    private static final int STOPFULLSCREEN    = 204;
-    private static final int RESTORESTATE      = 205;
-
-    // Timer thread -> UI thread
-    private static final int TIMEUPDATE = 300;
-
-    // The C++ MediaPlayerPrivateAndroid object.
-    int mNativePointer;
-    // The handler for WebCore thread messages;
-    private Handler mWebCoreHandler;
-    // The WebViewClassic instance that created this view.
-    private WebViewClassic mWebView;
-    // The poster image to be shown when the video is not playing.
-    // This ref prevents the bitmap from being GC'ed.
-    private Bitmap mPoster;
-    // The poster downloader.
-    private PosterDownloader mPosterDownloader;
-    // The seek position.
-    private int mSeekPosition;
-    // A helper class to control the playback. This executes on the UI thread!
-    private static final class VideoPlayer {
-        // The proxy that is currently playing (if any).
-        private static HTML5VideoViewProxy mCurrentProxy;
-        // The VideoView instance. This is a singleton for now, at least until
-        // http://b/issue?id=1973663 is fixed.
-        private static HTML5VideoView mHTML5VideoView;
-
-        private static boolean isVideoSelfEnded = false;
-
-        private static void setPlayerBuffering(boolean playerBuffering) {
-            mHTML5VideoView.setPlayerBuffering(playerBuffering);
-        }
-
-        // Every time webView setBaseLayer, this will be called.
-        // When we found the Video layer, then we set the Surface Texture to it.
-        // Otherwise, we may want to delete the Surface Texture to save memory.
-        public static void setBaseLayer(int layer) {
-            // Don't do this for full screen mode.
-            if (mHTML5VideoView != null
-                && !mHTML5VideoView.isFullScreenMode()
-                && !mHTML5VideoView.isReleased()) {
-                int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
-                SurfaceTexture surfTexture =
-                        HTML5VideoInline.getSurfaceTexture(currentVideoLayerId);
-                int textureName = mHTML5VideoView.getTextureName();
-
-                if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
-                    int playerState = mHTML5VideoView.getCurrentState();
-                    if (mHTML5VideoView.getPlayerBuffering())
-                        playerState = HTML5VideoView.STATE_PREPARING;
-                    boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
-                            layer, currentVideoLayerId, textureName,
-                            playerState);
-                    if (playerState >= HTML5VideoView.STATE_PREPARED
-                            && !foundInTree) {
-                        mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
-                    }
-                }
-            }
-        }
-
-        // When a WebView is paused, we also want to pause the video in it.
-        public static void pauseAndDispatch() {
-            if (mHTML5VideoView != null) {
-                mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
-            }
-        }
-
-        public static void enterFullScreenVideo(int layerId, String url,
-                HTML5VideoViewProxy proxy, WebViewClassic webView) {
-                // Save the inline video info and inherit it in the full screen
-                int savePosition = 0;
-                boolean canSkipPrepare = false;
-                boolean forceStart = false;
-                if (mHTML5VideoView != null) {
-                    // We don't allow enter full screen mode while the previous
-                    // full screen video hasn't finished yet.
-                    if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
-                        Log.w(LOGTAG, "Try to reenter the full screen mode");
-                        return;
-                    }
-                    int playerState = mHTML5VideoView.getCurrentState();
-                    // If we are playing the same video, then it is better to
-                    // save the current position.
-                    if (layerId == mHTML5VideoView.getVideoLayerId()) {
-                        savePosition = mHTML5VideoView.getCurrentPosition();
-                        canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
-                                || playerState == HTML5VideoView.STATE_PREPARED
-                                || playerState == HTML5VideoView.STATE_PLAYING)
-                                && !mHTML5VideoView.isFullScreenMode();
-                    }
-                    if (!canSkipPrepare) {
-                        mHTML5VideoView.reset();
-                    } else {
-                        forceStart = playerState == HTML5VideoView.STATE_PREPARING
-                                || playerState == HTML5VideoView.STATE_PLAYING;
-                    }
-                }
-                mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
-                        layerId, savePosition, canSkipPrepare);
-                mHTML5VideoView.setStartWhenPrepared(forceStart);
-                mCurrentProxy = proxy;
-                mHTML5VideoView.setVideoURI(url, mCurrentProxy);
-                mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
-        }
-
-        public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
-                WebViewClassic webView) {
-            if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
-                WebChromeClient client = webView.getWebChromeClient();
-                if (client != null) {
-                    if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
-                    client.onHideCustomView();
-                }
-            }
-        }
-
-        // This is on the UI thread.
-        // When native tell Java to play, we need to check whether or not it is
-        // still the same video by using videoLayerId and treat it differently.
-        public static void play(String url, int time, HTML5VideoViewProxy proxy,
-                WebChromeClient client, int videoLayerId) {
-            int currentVideoLayerId = -1;
-            boolean backFromFullScreenMode = false;
-            if (mHTML5VideoView != null) {
-                currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
-                backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
-
-                // When playing video back to back in full screen mode,
-                // javascript will switch the src and call play.
-                // In this case, we can just reuse the same full screen view,
-                // and play the video after prepared.
-                if (mHTML5VideoView.isFullScreenMode()
-                    && !backFromFullScreenMode
-                    && currentVideoLayerId != videoLayerId
-                    && mCurrentProxy != proxy) {
-                    mCurrentProxy = proxy;
-                    mHTML5VideoView.setStartWhenPrepared(true);
-                    mHTML5VideoView.setVideoURI(url, proxy);
-                    mHTML5VideoView.reprepareData(proxy);
-                    return;
-                }
-            }
-
-            boolean skipPrepare = false;
-            boolean createInlineView = false;
-            if (backFromFullScreenMode
-                && currentVideoLayerId == videoLayerId
-                && !mHTML5VideoView.isReleased()) {
-                skipPrepare = true;
-                createInlineView = true;
-            } else if(backFromFullScreenMode
-                || currentVideoLayerId != videoLayerId
-                || HTML5VideoInline.surfaceTextureDeleted()) {
-                // Here, we handle the case when switching to a new video,
-                // either inside a WebView or across WebViews
-                // For switching videos within a WebView or across the WebView,
-                // we need to pause the old one and re-create a new media player
-                // inside the HTML5VideoView.
-                if (mHTML5VideoView != null) {
-                    if (!backFromFullScreenMode) {
-                        mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
-                    }
-                    mHTML5VideoView.reset();
-                }
-                createInlineView = true;
-            }
-            if (createInlineView) {
-                mCurrentProxy = proxy;
-                mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare);
-
-                mHTML5VideoView.setVideoURI(url, mCurrentProxy);
-                mHTML5VideoView.prepareDataAndDisplayMode(proxy);
-                return;
-            }
-
-            if (mCurrentProxy == proxy) {
-                // Here, we handle the case when we keep playing with one video
-                if (!mHTML5VideoView.isPlaying()) {
-                    mHTML5VideoView.seekTo(time);
-                    mHTML5VideoView.start();
-                }
-            } else if (mCurrentProxy != null) {
-                // Some other video is already playing. Notify the caller that
-                // its playback ended.
-                proxy.dispatchOnEnded();
-            }
-        }
-
-        public static boolean isPlaying(HTML5VideoViewProxy proxy) {
-            return (mCurrentProxy == proxy && mHTML5VideoView != null
-                    && mHTML5VideoView.isPlaying());
-        }
-
-        public static int getCurrentPosition() {
-            int currentPosMs = 0;
-            if (mHTML5VideoView != null) {
-                currentPosMs = mHTML5VideoView.getCurrentPosition();
-            }
-            return currentPosMs;
-        }
-
-        public static void seek(int time, HTML5VideoViewProxy proxy) {
-            if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
-                mHTML5VideoView.seekTo(time);
-            }
-        }
-
-        public static void pause(HTML5VideoViewProxy proxy) {
-            if (mCurrentProxy == proxy && mHTML5VideoView != null) {
-                mHTML5VideoView.pause();
-            }
-        }
-
-        public static void onPrepared() {
-            if (!mHTML5VideoView.isFullScreenMode()) {
-                mHTML5VideoView.start();
-            }
-        }
-
-        public static void end() {
-            mHTML5VideoView.showControllerInFullScreen();
-            if (mCurrentProxy != null) {
-                if (isVideoSelfEnded)
-                    mCurrentProxy.dispatchOnEnded();
-                else
-                    mCurrentProxy.dispatchOnPaused();
-            }
-            isVideoSelfEnded = false;
-        }
-    }
-
-    // A bunch event listeners for our VideoView
-    // MediaPlayer.OnPreparedListener
-    @Override
-    public void onPrepared(MediaPlayer mp) {
-        VideoPlayer.onPrepared();
-        Message msg = Message.obtain(mWebCoreHandler, PREPARED);
-        Map<String, Object> map = new HashMap<String, Object>();
-        map.put("dur", new Integer(mp.getDuration()));
-        map.put("width", new Integer(mp.getVideoWidth()));
-        map.put("height", new Integer(mp.getVideoHeight()));
-        msg.obj = map;
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    // MediaPlayer.OnCompletionListener;
-    @Override
-    public void onCompletion(MediaPlayer mp) {
-        // The video ended by itself, so we need to
-        // send a message to the UI thread to dismiss
-        // the video view and to return to the WebView.
-        // arg1 == 1 means the video ends by itself.
-        sendMessage(obtainMessage(ENDED, 1, 0));
-    }
-
-    // MediaPlayer.OnErrorListener
-    @Override
-    public boolean onError(MediaPlayer mp, int what, int extra) {
-        sendMessage(obtainMessage(ERROR));
-        return false;
-    }
-
-    public void dispatchOnEnded() {
-        Message msg = Message.obtain(mWebCoreHandler, ENDED);
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    public void dispatchOnPaused() {
-        Message msg = Message.obtain(mWebCoreHandler, PAUSED);
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    public void dispatchOnStopFullScreen(boolean stillPlaying) {
-        Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
-        msg.arg1 = stillPlaying ? 1 : 0;
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    public void dispatchOnRestoreState() {
-        Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    public void onTimeupdate() {
-        sendMessage(obtainMessage(TIMEUPDATE));
-    }
-
-    // When there is a frame ready from surface texture, we should tell WebView
-    // to refresh.
-    @Override
-    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
-        // TODO: This should support partial invalidation too.
-        mWebView.invalidate();
-    }
-
-    // Handler for the messages from WebCore or Timer thread to the UI thread.
-    @Override
-    public void handleMessage(Message msg) {
-        // This executes on the UI thread.
-        switch (msg.what) {
-            case PLAY: {
-                String url = (String) msg.obj;
-                WebChromeClient client = mWebView.getWebChromeClient();
-                int videoLayerID = msg.arg1;
-                if (client != null) {
-                    VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
-                }
-                break;
-            }
-            case ENTER_FULLSCREEN:{
-                String url = (String) msg.obj;
-                WebChromeClient client = mWebView.getWebChromeClient();
-                int videoLayerID = msg.arg1;
-                if (client != null) {
-                    VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView);
-                }
-                break;
-            }
-            case SEEK: {
-                Integer time = (Integer) msg.obj;
-                mSeekPosition = time;
-                VideoPlayer.seek(mSeekPosition, this);
-                break;
-            }
-            case PAUSE: {
-                VideoPlayer.pause(this);
-                break;
-            }
-            case ENDED:
-                if (msg.arg1 == 1)
-                    VideoPlayer.isVideoSelfEnded = true;
-                VideoPlayer.end();
-                break;
-            case ERROR: {
-                WebChromeClient client = mWebView.getWebChromeClient();
-                if (client != null) {
-                    if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
-                    client.onHideCustomView();
-                }
-                break;
-            }
-            case LOAD_DEFAULT_POSTER: {
-                WebChromeClient client = mWebView.getWebChromeClient();
-                if (client != null) {
-                    if (DebugFlags.TRACE_CALLBACK) {
-                        Log.d(CallbackProxy.LOGTAG, "getDefaultVideoPoster");
-                    }
-                    doSetPoster(client.getDefaultVideoPoster());
-                }
-                break;
-            }
-            case TIMEUPDATE: {
-                if (VideoPlayer.isPlaying(this)) {
-                    sendTimeupdate();
-                }
-                break;
-            }
-            case BUFFERING_START: {
-                VideoPlayer.setPlayerBuffering(true);
-                break;
-            }
-            case BUFFERING_END: {
-                VideoPlayer.setPlayerBuffering(false);
-                break;
-            }
-        }
-    }
-
-    // Everything below this comment executes on the WebCore thread, except for
-    // the EventHandler methods, which are called on the network thread.
-
-    // A helper class that knows how to download posters
-    private static final class PosterDownloader implements EventHandler {
-        // The request queue. This is static as we have one queue for all posters.
-        private static RequestQueue mRequestQueue;
-        private static int mQueueRefCount = 0;
-        // The poster URL
-        private URL mUrl;
-        // The proxy we're doing this for.
-        private final HTML5VideoViewProxy mProxy;
-        // The poster bytes. We only touch this on the network thread.
-        private ByteArrayOutputStream mPosterBytes;
-        // The request handle. We only touch this on the WebCore thread.
-        private RequestHandle mRequestHandle;
-        // The response status code.
-        private int mStatusCode;
-        // The response headers.
-        private Headers mHeaders;
-        // The handler to handle messages on the WebCore thread.
-        private Handler mHandler;
-
-        public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
-            try {
-                mUrl = new URL(url);
-            } catch (MalformedURLException e) {
-                mUrl = null;
-            }
-            mProxy = proxy;
-            mHandler = new Handler();
-        }
-        // Start the download. Called on WebCore thread.
-        public void start() {
-            retainQueue();
-
-            if (mUrl == null) {
-                return;
-            }
-
-            // Only support downloading posters over http/https.
-            // FIXME: Add support for other schemes. WebKit seems able to load
-            // posters over other schemes e.g. file://, but gets the dimensions wrong.
-            String protocol = mUrl.getProtocol();
-            if ("http".equals(protocol) || "https".equals(protocol)) {
-                mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
-                        this, null, 0);
-            }
-        }
-        // Cancel the download if active and release the queue. Called on WebCore thread.
-        public void cancelAndReleaseQueue() {
-            if (mRequestHandle != null) {
-                mRequestHandle.cancel();
-                mRequestHandle = null;
-            }
-            releaseQueue();
-        }
-        // EventHandler methods. Executed on the network thread.
-        @Override
-        public void status(int major_version,
-                int minor_version,
-                int code,
-                String reason_phrase) {
-            mStatusCode = code;
-        }
-
-        @Override
-        public void headers(Headers headers) {
-            mHeaders = headers;
-        }
-
-        @Override
-        public void data(byte[] data, int len) {
-            if (mPosterBytes == null) {
-                mPosterBytes = new ByteArrayOutputStream();
-            }
-            mPosterBytes.write(data, 0, len);
-        }
-
-        @Override
-        public void endData() {
-            if (mStatusCode == 200) {
-                if (mPosterBytes.size() > 0) {
-                    Bitmap poster = BitmapFactory.decodeByteArray(
-                            mPosterBytes.toByteArray(), 0, mPosterBytes.size());
-                    mProxy.doSetPoster(poster);
-                }
-                cleanup();
-            } else if (mStatusCode >= 300 && mStatusCode < 400) {
-                // We have a redirect.
-                try {
-                    mUrl = new URL(mHeaders.getLocation());
-                } catch (MalformedURLException e) {
-                    mUrl = null;
-                }
-                if (mUrl != null) {
-                    mHandler.post(new Runnable() {
-                       @Override
-                       public void run() {
-                           if (mRequestHandle != null) {
-                               mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
-                                       new HashMap<String, String>());
-                           }
-                       }
-                    });
-                }
-            }
-        }
-
-        @Override
-        public void certificate(SslCertificate certificate) {
-            // Don't care.
-        }
-
-        @Override
-        public void error(int id, String description) {
-            cleanup();
-        }
-
-        @Override
-        public boolean handleSslErrorRequest(SslError error) {
-            // Don't care. If this happens, data() will never be called so
-            // mPosterBytes will never be created, so no need to call cleanup.
-            return false;
-        }
-        // Tears down the poster bytes stream. Called on network thread.
-        private void cleanup() {
-            if (mPosterBytes != null) {
-                try {
-                    mPosterBytes.close();
-                } catch (IOException ignored) {
-                    // Ignored.
-                } finally {
-                    mPosterBytes = null;
-                }
-            }
-        }
-
-        // Queue management methods. Called on WebCore thread.
-        private void retainQueue() {
-            if (mRequestQueue == null) {
-                mRequestQueue = new RequestQueue(mProxy.getContext());
-            }
-            mQueueRefCount++;
-        }
-
-        private void releaseQueue() {
-            if (mQueueRefCount == 0) {
-                return;
-            }
-            if (--mQueueRefCount == 0) {
-                mRequestQueue.shutdown();
-                mRequestQueue = null;
-            }
-        }
-    }
-
-    /**
-     * Private constructor.
-     * @param webView is the WebView that hosts the video.
-     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
-     */
-    private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) {
-        // This handler is for the main (UI) thread.
-        super(Looper.getMainLooper());
-        // Save the WebView object.
-        mWebView = webView;
-        // Pass Proxy into webview, such that every time we have a setBaseLayer
-        // call, we tell this Proxy to call the native to update the layer tree
-        // for the Video Layer's surface texture info
-        mWebView.setHTML5VideoViewProxy(this);
-        // Save the native ptr
-        mNativePointer = nativePtr;
-        // create the message handler for this thread
-        createWebCoreHandler();
-    }
-
-    private void createWebCoreHandler() {
-        mWebCoreHandler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case PREPARED: {
-                        Map<String, Object> map = (Map<String, Object>) msg.obj;
-                        Integer duration = (Integer) map.get("dur");
-                        Integer width = (Integer) map.get("width");
-                        Integer height = (Integer) map.get("height");
-                        nativeOnPrepared(duration.intValue(), width.intValue(),
-                                height.intValue(), mNativePointer);
-                        break;
-                    }
-                    case ENDED:
-                        mSeekPosition = 0;
-                        nativeOnEnded(mNativePointer);
-                        break;
-                    case PAUSED:
-                        nativeOnPaused(mNativePointer);
-                        break;
-                    case POSTER_FETCHED:
-                        Bitmap poster = (Bitmap) msg.obj;
-                        nativeOnPosterFetched(poster, mNativePointer);
-                        break;
-                    case TIMEUPDATE:
-                        nativeOnTimeupdate(msg.arg1, mNativePointer);
-                        break;
-                    case STOPFULLSCREEN:
-                        nativeOnStopFullscreen(msg.arg1, mNativePointer);
-                        break;
-                    case RESTORESTATE:
-                        nativeOnRestoreState(mNativePointer);
-                        break;
-                }
-            }
-        };
-    }
-
-    private void doSetPoster(Bitmap poster) {
-        if (poster == null) {
-            return;
-        }
-        // Save a ref to the bitmap and send it over to the WebCore thread.
-        mPoster = poster;
-        Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
-        msg.obj = poster;
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    private void sendTimeupdate() {
-        Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
-        msg.arg1 = VideoPlayer.getCurrentPosition();
-        mWebCoreHandler.sendMessage(msg);
-    }
-
-    public Context getContext() {
-        return mWebView.getContext();
-    }
-
-    // The public methods below are all called from WebKit only.
-    /**
-     * Play a video stream.
-     * @param url is the URL of the video stream.
-     */
-    public void play(String url, int position, int videoLayerID) {
-        if (url == null) {
-            return;
-        }
-
-        if (position > 0) {
-            seek(position);
-        }
-        Message message = obtainMessage(PLAY);
-        message.arg1 = videoLayerID;
-        message.obj = url;
-        sendMessage(message);
-    }
-
-    /**
-     * Play a video stream in full screen mode.
-     * @param url is the URL of the video stream.
-     */
-    public void enterFullscreenForVideoLayer(String url, int videoLayerID) {
-        if (url == null) {
-            return;
-        }
-
-        Message message = obtainMessage(ENTER_FULLSCREEN);
-        message.arg1 = videoLayerID;
-        message.obj = url;
-        sendMessage(message);
-    }
-
-    /**
-     * Seek into the video stream.
-     * @param  time is the position in the video stream.
-     */
-    public void seek(int time) {
-        Message message = obtainMessage(SEEK);
-        message.obj = new Integer(time);
-        sendMessage(message);
-    }
-
-    /**
-     * Pause the playback.
-     */
-    public void pause() {
-        Message message = obtainMessage(PAUSE);
-        sendMessage(message);
-    }
-
-    /**
-     * Tear down this proxy object.
-     */
-    public void teardown() {
-        // This is called by the C++ MediaPlayerPrivate dtor.
-        // Cancel any active poster download.
-        if (mPosterDownloader != null) {
-            mPosterDownloader.cancelAndReleaseQueue();
-        }
-        mNativePointer = 0;
-    }
-
-    /**
-     * Load the poster image.
-     * @param url is the URL of the poster image.
-     */
-    public void loadPoster(String url) {
-        if (url == null) {
-            Message message = obtainMessage(LOAD_DEFAULT_POSTER);
-            sendMessage(message);
-            return;
-        }
-        // Cancel any active poster download.
-        if (mPosterDownloader != null) {
-            mPosterDownloader.cancelAndReleaseQueue();
-        }
-        // Load the poster asynchronously
-        mPosterDownloader = new PosterDownloader(url, this);
-        mPosterDownloader.start();
-    }
-
-    // These three function are called from UI thread only by WebView.
-    public void setBaseLayer(int layer) {
-        VideoPlayer.setBaseLayer(layer);
-    }
-
-    public void pauseAndDispatch() {
-        VideoPlayer.pauseAndDispatch();
-    }
-
-    public void enterFullScreenVideo(int layerId, String url) {
-        VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
-    }
-
-    public void exitFullScreenVideo() {
-        VideoPlayer.exitFullScreenVideo(this, mWebView);
-    }
-
-    /**
-     * The factory for HTML5VideoViewProxy instances.
-     * @param webViewCore is the WebViewCore that is requesting the proxy.
-     *
-     * @return a new HTML5VideoViewProxy object.
-     */
-    public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
-        return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
-    }
-
-    /* package */ WebViewClassic getWebView() {
-        return mWebView;
-    }
-
-    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
-    private native void nativeOnEnded(int nativePointer);
-    private native void nativeOnPaused(int nativePointer);
-    private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
-    private native void nativeOnTimeupdate(int position, int nativePointer);
-    private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
-    private native void nativeOnRestoreState(int nativePointer);
-    private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
-            int baseLayer, int videoLayerId, int textureName,
-            int playerState);
-
-    @Override
-    public boolean onInfo(MediaPlayer mp, int what, int extra) {
-        if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
-            sendMessage(obtainMessage(BUFFERING_START, what, extra));
-        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
-            sendMessage(obtainMessage(BUFFERING_END, what, extra));
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                return true;
-            } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
-                VideoPlayer.exitFullScreenVideo(this, mWebView);
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
deleted file mode 100644
index e6eaa14..0000000
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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;
-
-import android.net.ProxyProperties;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Set;
-
-final class JWebCoreJavaBridge extends Handler {
-    // Identifier for the timer message.
-    private static final int TIMER_MESSAGE = 1;
-    // ID for servicing functionptr queue
-    private static final int FUNCPTR_MESSAGE = 2;
-    // Log system identifier.
-    private static final String LOGTAG = "webkit-timers";
-
-    // Native object pointer for interacting in native code.
-    private int mNativeBridge;
-    // Instant timer is used to implement a timer that needs to fire almost
-    // immediately.
-    private boolean mHasInstantTimer;
-
-    private boolean mTimerPaused;
-    private boolean mHasDeferredTimers;
-
-    // keep track of the main WebViewClassic attached to the current window so that we
-    // can get the proper Context.
-    private static WeakReference<WebViewClassic> sCurrentMainWebView =
-            new WeakReference<WebViewClassic>(null);
-
-    /* package */
-    static final int REFRESH_PLUGINS = 100;
-
-    private HashMap<String, String> mContentUriToFilePathMap;
-
-    /**
-     * Construct a new JWebCoreJavaBridge to interface with
-     * WebCore timers and cookies.
-     */
-    public JWebCoreJavaBridge() {
-        nativeConstructor();
-
-    }
-
-    @Override
-    protected void finalize() {
-        nativeFinalize();
-    }
-
-    static synchronized void setActiveWebView(WebViewClassic webview) {
-        if (sCurrentMainWebView.get() != null) {
-            // it is possible if there is a sub-WebView. Do nothing.
-            return;
-        }
-        sCurrentMainWebView = new WeakReference<WebViewClassic>(webview);
-    }
-
-    static synchronized void removeActiveWebView(WebViewClassic webview) {
-        if (sCurrentMainWebView.get() != webview) {
-            // it is possible if there is a sub-WebView. Do nothing.
-            return;
-        }
-        sCurrentMainWebView.clear();
-    }
-
-    /**
-     * Call native timer callbacks.
-     */
-    private void fireSharedTimer() { 
-        // clear the flag so that sharedTimerFired() can set a new timer
-        mHasInstantTimer = false;
-        sharedTimerFired();
-    }
-
-    /**
-     * handleMessage
-     * @param msg The dispatched message.
-     *
-     * The only accepted message currently is TIMER_MESSAGE
-     */
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case TIMER_MESSAGE: {
-                if (mTimerPaused) {
-                    mHasDeferredTimers = true;
-                } else {
-                    fireSharedTimer();
-                }
-                break;
-            }
-            case FUNCPTR_MESSAGE:
-                nativeServiceFuncPtrQueue();
-                break;
-            case REFRESH_PLUGINS:
-                nativeUpdatePluginDirectories(PluginManager.getInstance(null)
-                        .getPluginDirectories(), ((Boolean) msg.obj)
-                        .booleanValue());
-                break;
-        }
-    }
-    
-    // called from JNI side
-    private void signalServiceFuncPtrQueue() {
-        Message msg = obtainMessage(FUNCPTR_MESSAGE);
-        sendMessage(msg);
-    }
-    
-    private native void nativeServiceFuncPtrQueue();
-
-    /**
-     * Pause all timers.
-     */
-    public void pause() {
-        if (!mTimerPaused) {
-            mTimerPaused = true;
-            mHasDeferredTimers = false;
-        }
-    }
-
-    /**
-     * Resume all timers.
-     */
-    public void resume() {
-        if (mTimerPaused) {
-           mTimerPaused = false;
-           if (mHasDeferredTimers) {
-               mHasDeferredTimers = false;
-               fireSharedTimer();
-           }
-        }
-    }
-
-    /**
-     * Set WebCore cache size.
-     * @param bytes The cache size in bytes.
-     */
-    public native void setCacheSize(int bytes);
-
-    /**
-     * Store a cookie string associated with a url.
-     * @param url The url to be used as a key for the cookie.
-     * @param value The cookie string to be stored.
-     */
-    private void setCookies(String url, String value) {
-        if (value.contains("\r") || value.contains("\n")) {
-            // for security reason, filter out '\r' and '\n' from the cookie
-            int size = value.length();
-            StringBuilder buffer = new StringBuilder(size);
-            int i = 0;
-            while (i != -1 && i < size) {
-                int ir = value.indexOf('\r', i);
-                int in = value.indexOf('\n', i);
-                int newi = (ir == -1) ? in : (in == -1 ? ir : (ir < in ? ir
-                        : in));
-                if (newi > i) {
-                    buffer.append(value.subSequence(i, newi));
-                } else if (newi == -1) {
-                    buffer.append(value.subSequence(i, size));
-                    break;
-                }
-                i = newi + 1;
-            }
-            value = buffer.toString();
-        }
-        CookieManager.getInstance().setCookie(url, value);
-    }
-
-    /**
-     * Retrieve the cookie string for the given url.
-     * @param url The resource's url.
-     * @return A String representing the cookies for the given resource url.
-     */
-    private String cookies(String url) {
-        return CookieManager.getInstance().getCookie(url);
-    }
-
-    /**
-     * Returns whether cookies are enabled or not.
-     */
-    private boolean cookiesEnabled() {
-        return CookieManager.getInstance().acceptCookie();
-    }
-
-    /**
-     * Returns an array of plugin directoies
-     */
-    private String[] getPluginDirectories() {
-        return PluginManager.getInstance(null).getPluginDirectories();
-    }
-
-    /**
-     * Returns the path of the plugin data directory
-     */
-    private String getPluginSharedDataDirectory() {
-        return PluginManager.getInstance(null).getPluginSharedDataDirectory();
-    }
-
-    /**
-     * setSharedTimer
-     * @param timemillis The relative time when the timer should fire
-     */
-    private void setSharedTimer(long timemillis) {
-        if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) Log.v(LOGTAG, "setSharedTimer " + timemillis);
-
-        if (timemillis <= 0) {
-            // we don't accumulate the sharedTimer unless it is a delayed
-            // request. This way we won't flood the message queue with
-            // WebKit messages. This should improve the browser's
-            // responsiveness to key events.
-            if (mHasInstantTimer) {
-                return;
-            } else {
-                mHasInstantTimer = true;
-                Message msg = obtainMessage(TIMER_MESSAGE);
-                sendMessageDelayed(msg, timemillis);
-            }
-        } else {
-            Message msg = obtainMessage(TIMER_MESSAGE);
-            sendMessageDelayed(msg, timemillis);
-        }
-    }
-
-    /**
-     * Stop the shared timer.
-     */
-    private void stopSharedTimer() {
-        if (DebugFlags.J_WEB_CORE_JAVA_BRIDGE) {
-            Log.v(LOGTAG, "stopSharedTimer removing all timers");
-        }
-        removeMessages(TIMER_MESSAGE);
-        mHasInstantTimer = false;
-        mHasDeferredTimers = false;
-    }
-
-    private String[] getKeyStrengthList() {
-        return CertTool.getKeyStrengthList();
-    }
-
-    synchronized private String getSignedPublicKey(int index, String challenge,
-            String url) {
-        WebViewClassic current = sCurrentMainWebView.get();
-        if (current != null) {
-            // generateKeyPair expects organizations which we don't have. Ignore
-            // url.
-            return CertTool.getSignedPublicKey(
-                    current.getContext(), index, challenge);
-        } else {
-            Log.e(LOGTAG, "There is no active WebView for getSignedPublicKey");
-            return "";
-        }
-    }
-
-    // Called on the WebCore thread through JNI.
-    private String resolveFilePathForContentUri(String uri) {
-        if (mContentUriToFilePathMap != null) {
-            String fileName = mContentUriToFilePathMap.get(uri);
-            if (fileName != null) {
-                return fileName;
-            }
-        }
-
-        // Failsafe fallback to just use the last path segment.
-        // (See OpenableColumns documentation in the SDK)
-        Uri jUri = Uri.parse(uri);
-        return jUri.getLastPathSegment();
-    }
-
-    public void storeFilePathForContentUri(String path, String contentUri) {
-        if (mContentUriToFilePathMap == null) {
-            mContentUriToFilePathMap = new HashMap<String, String>();
-        }
-        mContentUriToFilePathMap.put(contentUri, path);
-    }
-
-    public void updateProxy(ProxyProperties proxyProperties) {
-        if (proxyProperties == null) {
-            nativeUpdateProxy("", "");
-            return;
-        }
-
-        String host = proxyProperties.getHost();
-        int port = proxyProperties.getPort();
-        if (port != 0)
-            host += ":" + port;
-
-        nativeUpdateProxy(host, proxyProperties.getExclusionList());
-    }
-
-    private native void nativeConstructor();
-    private native void nativeFinalize();
-    private native void sharedTimerFired();
-    private native void nativeUpdatePluginDirectories(String[] directories,
-            boolean reload);
-    public native void setNetworkOnLine(boolean online);
-    public native void setNetworkType(String type, String subtype);
-    public native void addPackageNames(Set<String> packageNames);
-    public native void addPackageName(String packageName);
-    public native void removePackageName(String packageName);
-    public native void nativeUpdateProxy(String newProxy, String exclusionList);
-}
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
deleted file mode 100644
index 01a81c4..0000000
--- a/core/java/android/webkit/JniUtil.java
+++ /dev/null
@@ -1,189 +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.
- */
-
-package android.webkit;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.net.Uri;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.File;
-import java.io.InputStream;
-
-class JniUtil {
-
-    static {
-        System.loadLibrary("webcore");
-        System.loadLibrary("chromium_net");
-    }
-    private static final String LOGTAG = "webkit";
-    private JniUtil() {} // Utility class, do not instantiate.
-
-    // Used by the Chromium HTTP stack.
-    private static String sDatabaseDirectory;
-    private static String sCacheDirectory;
-    private static Context sContext;
-
-    private static void checkInitialized() {
-        if (sContext == null) {
-            throw new IllegalStateException("Call CookieSyncManager::createInstance() or create a webview before using this class");
-        }
-    }
-
-    protected static synchronized void setContext(Context context) {
-        if (sContext != null) {
-            return;
-        }
-
-        sContext = context.getApplicationContext();
-    }
-
-    protected static synchronized Context getContext() {
-        return sContext;
-    }
-
-    /**
-     * Called by JNI. Gets the application's database directory, excluding the trailing slash.
-     * @return String The application's database directory
-     */
-    private static synchronized String getDatabaseDirectory() {
-        checkInitialized();
-
-        if (sDatabaseDirectory == null) {
-            sDatabaseDirectory = sContext.getDatabasePath("dummy").getParent();
-        }
-
-        return sDatabaseDirectory;
-    }
-
-    /**
-     * Called by JNI. Gets the application's cache directory, excluding the trailing slash.
-     * @return String The application's cache directory
-     */
-    private static synchronized String getCacheDirectory() {
-        checkInitialized();
-
-        if (sCacheDirectory == null) {
-            File cacheDir = sContext.getCacheDir();
-            if (cacheDir == null) {
-                sCacheDirectory = "";
-            } else {
-                sCacheDirectory = cacheDir.getAbsolutePath();
-            }
-        }
-
-        return sCacheDirectory;
-    }
-
-    /**
-     * Called by JNI. Gets the application's package name.
-     * @return String The application's package name
-     */
-    private static synchronized String getPackageName() {
-        checkInitialized();
-
-        return sContext.getPackageName();
-    }
-
-    private static final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
-
-    /**
-     * Called by JNI. Calculates the size of an input stream by reading it.
-     * @return long The size of the stream
-     */
-    private static synchronized long contentUrlSize(String url) {
-        // content://
-        if (url.startsWith(ANDROID_CONTENT)) {
-            try {
-                // Strip off MIME type. If we don't do this, we can fail to
-                // load Gmail attachments, because the URL being loaded doesn't
-                // exactly match the URL we have permission to read.
-                int mimeIndex = url.lastIndexOf('?');
-                if (mimeIndex != -1) {
-                    url = url.substring(0, mimeIndex);
-                }
-                Uri uri = Uri.parse(url);
-                InputStream is = sContext.getContentResolver().openInputStream(uri);
-                byte[] buffer = new byte[1024];
-                int n;
-                long size = 0;
-                try {
-                    while ((n = is.read(buffer)) != -1) {
-                        size += n;
-                    }
-                } finally {
-                    is.close();
-                }
-                return size;
-            } catch (Exception e) {
-                Log.e(LOGTAG, "Exception: " + url);
-                return 0;
-            }
-        } else {
-            return 0;
-        }
-    }
-
-    /**
-     * Called by JNI.
-     *
-     * @return  Opened input stream to content
-     * TODO: Make all content loading use this instead of BrowserFrame.java
-     */
-    private static synchronized InputStream contentUrlStream(String url) {
-        // content://
-        if (url.startsWith(ANDROID_CONTENT)) {
-            try {
-                // Strip off mimetype, for compatibility with ContentLoader.java
-                // (used with Android HTTP stack, now removed).
-                // If we don't do this, we can fail to load Gmail attachments,
-                // because the URL being loaded doesn't exactly match the URL we
-                // have permission to read.
-                int mimeIndex = url.lastIndexOf('?');
-                if (mimeIndex != -1) {
-                    url = url.substring(0, mimeIndex);
-                }
-                Uri uri = Uri.parse(url);
-                return sContext.getContentResolver().openInputStream(uri);
-            } catch (Exception e) {
-                Log.e(LOGTAG, "Exception: " + url);
-                return null;
-            }
-        } else {
-            return null;
-        }
-    }
-
-    private static synchronized String getAutofillQueryUrl() {
-        checkInitialized();
-        // If the device has not checked in it won't have pulled down the system setting for the
-        // Autofill Url. In that case we will not make autofill server requests.
-        return Settings.Global.getString(sContext.getContentResolver(),
-                Settings.Global.WEB_AUTOFILL_QUERY_URL);
-    }
-
-    private static boolean canSatisfyMemoryAllocation(long bytesRequested) {
-        checkInitialized();
-        ActivityManager manager = (ActivityManager) sContext.getSystemService(
-                Context.ACTIVITY_SERVICE);
-        ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
-        manager.getMemoryInfo(memInfo);
-        long leftToAllocate = memInfo.availMem - memInfo.threshold;
-        return !memInfo.lowMemory && bytesRequested < leftToAllocate;
-    }
-}
diff --git a/core/java/android/webkit/KeyStoreHandler.java b/core/java/android/webkit/KeyStoreHandler.java
deleted file mode 100644
index 849007e..0000000
--- a/core/java/android/webkit/KeyStoreHandler.java
+++ /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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * KeyStoreHandler: class responsible for certificate installation to
- * the system key store. It reads the certificates file from network
- * then pass the bytes to class CertTool.
- * This class is only needed if the Chromium HTTP stack is used.
- */
-class KeyStoreHandler extends Handler {
-    private static final String LOGTAG = "KeyStoreHandler";
-
-    private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
-
-    private String mMimeType;
-
-    public KeyStoreHandler(String mimeType) {
-      mMimeType = mimeType;
-    }
-
-    /**
-     * Add data to the internal collection of data.
-     * @param data A byte array containing the content.
-     * @param length The length of data.
-     */
-    public void didReceiveData(byte[] data, int length) {
-        synchronized (mDataBuilder) {
-            mDataBuilder.append(data, 0, length);
-        }
-    }
-
-    public void installCert(Context context) {
-        String type = CertTool.getCertType(mMimeType);
-        if (type == null) return;
-
-        // This must be synchronized so that no more data can be added
-        // after getByteSize returns.
-        synchronized (mDataBuilder) {
-            // In the case of downloading certificate, we will save it
-            // to the KeyStore and stop the current loading so that it
-            // will not generate a new history page
-            byte[] cert = new byte[mDataBuilder.getByteSize()];
-            int offset = 0;
-            while (true) {
-                ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
-                if (c == null) break;
-
-                if (c.mLength != 0) {
-                    System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
-                    offset += c.mLength;
-                }
-                c.release();
-            }
-            CertTool.addCertificate(context, type, cert);
-            return;
-        }
-    }
-}
diff --git a/core/java/android/webkit/L10nUtils.java b/core/java/android/webkit/L10nUtils.java
deleted file mode 100644
index a1c6a53..0000000
--- a/core/java/android/webkit/L10nUtils.java
+++ /dev/null
@@ -1,120 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-
-import java.lang.ref.SoftReference;
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * @hide
- */
-public class L10nUtils {
-
-    // These array elements must be kept in sync with those defined in
-    // external/chromium/android/app/l10n_utils.h
-    private static int[] mIdsArray = {
-        com.android.internal.R.string.autofill_address_name_separator,      // IDS_AUTOFILL_DIALOG_ADDRESS_NAME_SEPARATOR
-        com.android.internal.R.string.autofill_address_summary_name_format, // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_NAME_FORMAT
-        com.android.internal.R.string.autofill_address_summary_separator,   // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_SEPARATOR
-        com.android.internal.R.string.autofill_address_summary_format,      // IDS_AUTOFILL_DIALOG_ADDRESS_SUMMARY_FORMAT
-        com.android.internal.R.string.autofill_attention_ignored_re,        // IDS_AUTOFILL_ATTENTION_IGNORED_RE
-        com.android.internal.R.string.autofill_region_ignored_re,           // IDS_AUTOFILL_REGION_IGNORED_RE
-        com.android.internal.R.string.autofill_company_re,                  // IDS_AUTOFILL_COMPANY_RE
-        com.android.internal.R.string.autofill_address_line_1_re,           // IDS_AUTOFILL_ADDRESS_LINE_1_RE
-        com.android.internal.R.string.autofill_address_line_1_label_re,     // IDS_AUTOFILL_ADDRESS_LINE_1_LABEL_RE
-        com.android.internal.R.string.autofill_address_line_2_re,           // IDS_AUTOFILL_ADDRESS_LINE_2_RE
-        com.android.internal.R.string.autofill_address_line_3_re,           // IDS_AUTOFILL_ADDRESS_LINE_3_RE
-        com.android.internal.R.string.autofill_country_re,                  // IDS_AUTOFILL_COUNTRY_RE
-        com.android.internal.R.string.autofill_zip_code_re,                 // IDS_AUTOFILL_ZIP_CODE_RE
-        com.android.internal.R.string.autofill_zip_4_re,                    // IDS_AUTOFILL_ZIP_4_RE
-        com.android.internal.R.string.autofill_city_re,                     // IDS_AUTOFILL_CITY_RE
-        com.android.internal.R.string.autofill_state_re,                    // IDS_AUTOFILL_STATE_RE
-        com.android.internal.R.string.autofill_address_type_same_as_re,     // IDS_AUTOFILL_SAME_AS_RE
-        com.android.internal.R.string.autofill_address_type_use_my_re,      // IDS_AUTOFILL_USE_MY_RE
-        com.android.internal.R.string.autofill_billing_designator_re,       // IDS_AUTOFILL_BILLING_DESIGNATOR_RE
-        com.android.internal.R.string.autofill_shipping_designator_re,      // IDS_AUTOFILL_SHIPPING_DESIGNATOR_RE
-        com.android.internal.R.string.autofill_email_re,                    // IDS_AUTOFILL_EMAIL_RE
-        com.android.internal.R.string.autofill_username_re,                 // IDS_AUTOFILL_USERNAME_RE
-        com.android.internal.R.string.autofill_name_re,                     // IDS_AUTOFILL_NAME_RE
-        com.android.internal.R.string.autofill_name_specific_re,            // IDS_AUTOFILL_NAME_SPECIFIC_RE
-        com.android.internal.R.string.autofill_first_name_re,               // IDS_AUTOFILL_FIRST_NAME_RE
-        com.android.internal.R.string.autofill_middle_initial_re,           // IDS_AUTOFILL_MIDDLE_INITIAL_RE
-        com.android.internal.R.string.autofill_middle_name_re,              // IDS_AUTOFILL_MIDDLE_NAME_RE
-        com.android.internal.R.string.autofill_last_name_re,                // IDS_AUTOFILL_LAST_NAME_RE
-        com.android.internal.R.string.autofill_phone_re,                    // IDS_AUTOFILL_PHONE_RE
-        com.android.internal.R.string.autofill_area_code_re,                // IDS_AUTOFILL_AREA_CODE_RE
-        com.android.internal.R.string.autofill_phone_prefix_re,             // IDS_AUTOFILL_PHONE_PREFIX_RE
-        com.android.internal.R.string.autofill_phone_suffix_re,             // IDS_AUTOFILL_PHONE_SUFFIX_RE
-        com.android.internal.R.string.autofill_phone_extension_re,          // IDS_AUTOFILL_PHONE_EXTENSION_RE
-        com.android.internal.R.string.autofill_name_on_card_re,             // IDS_AUTOFILL_NAME_ON_CARD_RE
-        com.android.internal.R.string.autofill_name_on_card_contextual_re,  // IDS_AUTOFILL_NAME_ON_CARD_CONTEXTUAL_RE
-        com.android.internal.R.string.autofill_card_cvc_re,                 // IDS_AUTOFILL_CARD_CVC_RE
-        com.android.internal.R.string.autofill_card_number_re,              // IDS_AUTOFILL_CARD_NUMBER_RE
-        com.android.internal.R.string.autofill_expiration_month_re,         // IDS_AUTOFILL_EXPIRATION_MONTH_RE
-        com.android.internal.R.string.autofill_expiration_date_re,          // IDS_AUTOFILL_EXPIRATION_DATE_RE
-        com.android.internal.R.string.autofill_card_ignored_re,             // IDS_AUTOFILL_CARD_IGNORED_RE
-        com.android.internal.R.string.autofill_fax_re,                      // IDS_AUTOFILL_FAX_RE
-        com.android.internal.R.string.autofill_country_code_re,             // IDS_AUTOFILL_COUNTRY_CODE_RE
-        com.android.internal.R.string.autofill_area_code_notext_re,         // IDS_AUTOFILL_AREA_CODE_NOTEXT_RE
-        com.android.internal.R.string.autofill_phone_prefix_separator_re,   // IDS_AUTOFILL_PHONE_PREFIX_SEPARATOR_RE
-        com.android.internal.R.string.autofill_phone_suffix_separator_re,   // IDS_AUTOFILL_PHONE_SUFFIX_SEPARATOR_RE
-        com.android.internal.R.string.autofill_province,                    // IDS_AUTOFILL_DIALOG_PROVINCE
-        com.android.internal.R.string.autofill_postal_code,                 // IDS_AUTOFILL_DIALOG_POSTAL_CODE
-        com.android.internal.R.string.autofill_state,                       // IDS_AUTOFILL_DIALOG_STATE
-        com.android.internal.R.string.autofill_zip_code,                    // IDS_AUTOFILL_DIALOG_ZIP_CODE
-        com.android.internal.R.string.autofill_county,                      // IDS_AUTOFILL_DIALOG_COUNTY
-        com.android.internal.R.string.autofill_island,                      // IDS_AUTOFILL_DIALOG_ISLAND
-        com.android.internal.R.string.autofill_district,                    // IDS_AUTOFILL_DIALOG_DISTRICT
-        com.android.internal.R.string.autofill_department,                  // IDS_AUTOFILL_DIALOG_DEPARTMENT
-        com.android.internal.R.string.autofill_prefecture,                  // IDS_AUTOFILL_DIALOG_PREFECTURE
-        com.android.internal.R.string.autofill_parish,                      // IDS_AUTOFILL_DIALOG_PARISH
-        com.android.internal.R.string.autofill_area,                        // IDS_AUTOFILL_DIALOG_AREA
-        com.android.internal.R.string.autofill_emirate                      // IDS_AUTOFILL_DIALOG_EMIRATE
-    };
-
-    private static Context mApplicationContext;
-    private static Map<Integer, SoftReference<String> > mStrings;
-
-    public static void setApplicationContext(Context applicationContext) {
-        mApplicationContext = applicationContext.getApplicationContext();
-    }
-
-    private static String loadString(int id) {
-        if (mStrings == null) {
-            mStrings = new HashMap<Integer, SoftReference<String> >(mIdsArray.length);
-        }
-
-        String localisedString = mApplicationContext.getResources().getString(mIdsArray[id]);
-        mStrings.put(id, new SoftReference<String>(localisedString));
-        return localisedString;
-    }
-
-    public static String getLocalisedString(int id) {
-        if (mStrings == null) {
-            // This is the first time we need a localised string.
-            // loadString will create the Map.
-            return loadString(id);
-        }
-
-        SoftReference<String> ref = mStrings.get(id);
-        boolean needToLoad = ref == null || ref.get() == null;
-        return needToLoad ? loadString(id) : ref.get();
-    }
-}
diff --git a/core/java/android/webkit/MockGeolocation.java b/core/java/android/webkit/MockGeolocation.java
deleted file mode 100644
index 885c6c2..0000000
--- a/core/java/android/webkit/MockGeolocation.java
+++ /dev/null
@@ -1,64 +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.
- */
-
-package android.webkit;
-
-/**
- * Used to configure the mock Geolocation client for the LayoutTests.
- * @hide
- */
-public final class MockGeolocation {
-    private WebViewCore mWebViewCore;
-
-    public MockGeolocation(WebViewCore webViewCore) {
-        mWebViewCore = webViewCore;
-    }
-
-    /**
-     * Sets use of the mock Geolocation client. Also resets that client.
-     */
-    public void setUseMock() {
-        nativeSetUseMock(mWebViewCore);
-    }
-
-    /**
-     * Set the position for the mock Geolocation service.
-     */
-    public void setPosition(double latitude, double longitude, double accuracy) {
-        // This should only ever be called on the WebKit thread.
-        nativeSetPosition(mWebViewCore, latitude, longitude, accuracy);
-    }
-
-    /**
-     * Set the error for the mock Geolocation service.
-     */
-    public void setError(int code, String message) {
-        // This should only ever be called on the WebKit thread.
-        nativeSetError(mWebViewCore, code, message);
-    }
-
-    public void setPermission(boolean allow) {
-        // This should only ever be called on the WebKit thread.
-        nativeSetPermission(mWebViewCore, allow);
-    }
-
-    // Native functions
-    private static native void nativeSetUseMock(WebViewCore webViewCore);
-    private static native void nativeSetPosition(WebViewCore webViewCore, double latitude,
-            double longitude, double accuracy);
-    private static native void nativeSetError(WebViewCore webViewCore, int code, String message);
-    private static native void nativeSetPermission(WebViewCore webViewCore, boolean allow);
-}
diff --git a/core/java/android/webkit/OverScrollGlow.java b/core/java/android/webkit/OverScrollGlow.java
deleted file mode 100644
index d91f860..0000000
--- a/core/java/android/webkit/OverScrollGlow.java
+++ /dev/null
@@ -1,222 +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.
- */
-package android.webkit;
-
-import com.android.internal.R;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.widget.EdgeEffect;
-
-/**
- * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
- * @hide
- */
-public class OverScrollGlow {
-    private WebViewClassic mHostView;
-
-    private EdgeEffect mEdgeGlowTop;
-    private EdgeEffect mEdgeGlowBottom;
-    private EdgeEffect mEdgeGlowLeft;
-    private EdgeEffect mEdgeGlowRight;
-
-    private int mOverScrollDeltaX;
-    private int mOverScrollDeltaY;
-
-    public OverScrollGlow(WebViewClassic host) {
-        mHostView = host;
-        Context context = host.getContext();
-        mEdgeGlowTop = new EdgeEffect(context);
-        mEdgeGlowBottom = new EdgeEffect(context);
-        mEdgeGlowLeft = new EdgeEffect(context);
-        mEdgeGlowRight = new EdgeEffect(context);
-    }
-
-    /**
-     * Pull leftover touch scroll distance into one of the edge glows as appropriate.
-     *
-     * @param x Current X scroll offset
-     * @param y Current Y scroll offset
-     * @param oldX Old X scroll offset
-     * @param oldY Old Y scroll offset
-     * @param maxX Maximum range for horizontal scrolling
-     * @param maxY Maximum range for vertical scrolling
-     */
-    public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) {
-        // Only show overscroll bars if there was no movement in any direction
-        // as a result of scrolling.
-        if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) {
-            // Don't show left/right glows if we fit the whole content.
-            // Also don't show if there was vertical movement.
-            if (maxX > 0) {
-                final int pulledToX = oldX + mOverScrollDeltaX;
-                if (pulledToX < 0) {
-                    mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
-                    if (!mEdgeGlowRight.isFinished()) {
-                        mEdgeGlowRight.onRelease();
-                    }
-                } else if (pulledToX > maxX) {
-                    mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
-                    if (!mEdgeGlowLeft.isFinished()) {
-                        mEdgeGlowLeft.onRelease();
-                    }
-                }
-                mOverScrollDeltaX = 0;
-            }
-
-            if (maxY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
-                final int pulledToY = oldY + mOverScrollDeltaY;
-                if (pulledToY < 0) {
-                    mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
-                    if (!mEdgeGlowBottom.isFinished()) {
-                        mEdgeGlowBottom.onRelease();
-                    }
-                } else if (pulledToY > maxY) {
-                    mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
-                    if (!mEdgeGlowTop.isFinished()) {
-                        mEdgeGlowTop.onRelease();
-                    }
-                }
-                mOverScrollDeltaY = 0;
-            }
-        }
-    }
-
-    /**
-     * Set touch delta values indicating the current amount of overscroll.
-     *
-     * @param deltaX
-     * @param deltaY
-     */
-    public void setOverScrollDeltas(int deltaX, int deltaY) {
-        mOverScrollDeltaX = deltaX;
-        mOverScrollDeltaY = deltaY;
-    }
-
-    /**
-     * Absorb leftover fling velocity into one of the edge glows as appropriate.
-     *
-     * @param x Current X scroll offset
-     * @param y Current Y scroll offset
-     * @param oldX Old X scroll offset
-     * @param oldY Old Y scroll offset
-     * @param rangeX Maximum range for horizontal scrolling
-     * @param rangeY Maximum range for vertical scrolling
-     */
-    public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY) {
-        if (rangeY > 0 || mHostView.getWebView().getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
-            if (y < 0 && oldY >= 0) {
-                mEdgeGlowTop.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
-                if (!mEdgeGlowBottom.isFinished()) {
-                    mEdgeGlowBottom.onRelease();
-                }
-            } else if (y > rangeY && oldY <= rangeY) {
-                mEdgeGlowBottom.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
-                if (!mEdgeGlowTop.isFinished()) {
-                    mEdgeGlowTop.onRelease();
-                }
-            }
-        }
-
-        if (rangeX > 0) {
-            if (x < 0 && oldX >= 0) {
-                mEdgeGlowLeft.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
-                if (!mEdgeGlowRight.isFinished()) {
-                    mEdgeGlowRight.onRelease();
-                }
-            } else if (x > rangeX && oldX <= rangeX) {
-                mEdgeGlowRight.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
-                if (!mEdgeGlowLeft.isFinished()) {
-                    mEdgeGlowLeft.onRelease();
-                }
-            }
-        }
-    }
-
-    /**
-     * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
-     *
-     * @param canvas Canvas to draw into, transformed into view coordinates.
-     * @return true if glow effects are still animating and the view should invalidate again.
-     */
-    public boolean drawEdgeGlows(Canvas canvas) {
-        final int scrollX = mHostView.getScrollX();
-        final int scrollY = mHostView.getScrollY();
-        final int width = mHostView.getWidth();
-        int height = mHostView.getHeight();
-
-        boolean invalidateForGlow = false;
-        if (!mEdgeGlowTop.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.translate(scrollX, mHostView.getVisibleTitleHeight() + Math.min(0, scrollY));
-            mEdgeGlowTop.setSize(width, height);
-            invalidateForGlow |= mEdgeGlowTop.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        if (!mEdgeGlowBottom.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.translate(-width + scrollX, Math.max(mHostView.computeMaxScrollY(), scrollY)
-                    + height);
-            canvas.rotate(180, width, 0);
-            mEdgeGlowBottom.setSize(width, height);
-            invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        if (!mEdgeGlowLeft.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.rotate(270);
-            canvas.translate(-height - scrollY, Math.min(0, scrollX));
-            mEdgeGlowLeft.setSize(height, width);
-            invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        if (!mEdgeGlowRight.isFinished()) {
-            final int restoreCount = canvas.save();
-
-            canvas.rotate(90);
-            canvas.translate(scrollY,
-                    -(Math.max(mHostView.computeMaxScrollX(), scrollX) + width));
-            mEdgeGlowRight.setSize(height, width);
-            invalidateForGlow |= mEdgeGlowRight.draw(canvas);
-            canvas.restoreToCount(restoreCount);
-        }
-        return invalidateForGlow;
-    }
-
-    /**
-     * @return True if any glow is still animating
-     */
-    public boolean isAnimating() {
-        return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() ||
-                !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished());
-    }
-
-    /**
-     * Release all glows from any touch pulls in progress.
-     */
-    public void releaseAll() {
-        mEdgeGlowTop.onRelease();
-        mEdgeGlowBottom.onRelease();
-        mEdgeGlowLeft.onRelease();
-        mEdgeGlowRight.onRelease();
-    }
-}
diff --git a/core/java/android/webkit/PluginFullScreenHolder.java b/core/java/android/webkit/PluginFullScreenHolder.java
deleted file mode 100644
index 665cd9d..0000000
--- a/core/java/android/webkit/PluginFullScreenHolder.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package android.webkit;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-class PluginFullScreenHolder {
-
-    private final WebViewClassic mWebView;
-    private final int mNpp;
-    private final int mOrientation;
-
-    // The container for the plugin view
-    private static CustomFrameLayout mLayout;
-
-    private View mContentView;
-
-    PluginFullScreenHolder(WebViewClassic webView, int orientation, int npp) {
-        mWebView = webView;
-        mNpp = npp;
-        mOrientation = orientation;
-    }
-
-    public void setContentView(View contentView) {
-
-        // Create a FrameLayout that will contain the plugin's view
-        mLayout = new CustomFrameLayout(mWebView.getContext());
-        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
-                            ViewGroup.LayoutParams.MATCH_PARENT,
-                            ViewGroup.LayoutParams.MATCH_PARENT,
-                            Gravity.CENTER);
-
-        mLayout.addView(contentView, layoutParams);
-        mLayout.setVisibility(View.VISIBLE);
-
-        // fixed size is only used either during pinch zoom or surface is too
-        // big. Make sure it is not fixed size before setting it to the full
-        // screen content view. The SurfaceView will be set to the correct mode
-        // by the ViewManager when it is re-attached to the WebView.
-        if (contentView instanceof SurfaceView) {
-            final SurfaceView sView = (SurfaceView) contentView;
-            if (sView.isFixedSize()) {
-                sView.getHolder().setSizeFromLayout();
-            }
-        }
-
-        mContentView = contentView;
-    }
-
-    public void show() {
-        // Other plugins may attempt to draw so hide them while we're active.
-        if (mWebView.getViewManager() != null)
-            mWebView.getViewManager().hideAll();
-
-        WebChromeClient client = mWebView.getWebChromeClient();
-        client.onShowCustomView(mLayout, mOrientation, mCallback);
-    }
-
-    public void hide() {
-        WebChromeClient client = mWebView.getWebChromeClient();
-        client.onHideCustomView();
-    }
-
-    private class CustomFrameLayout extends FrameLayout {
-
-        CustomFrameLayout(Context context) {
-            super(context);
-        }
-
-        @Override
-        public boolean onKeyDown(int keyCode, KeyEvent event) {
-            if (event.isSystem()) {
-                return super.onKeyDown(keyCode, event);
-            }
-            mWebView.onKeyDown(keyCode, event);
-            // always return true as we are the handler
-            return true;
-        }
-
-        @Override
-        public boolean onKeyUp(int keyCode, KeyEvent event) {
-            if (event.isSystem()) {
-                return super.onKeyUp(keyCode, event);
-            }
-            mWebView.onKeyUp(keyCode, event);
-            // always return true as we are the handler
-            return true;
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            // always return true as we don't want the event to propagate any further
-            return true;
-        }
-
-        @Override
-        public boolean onTrackballEvent(MotionEvent event) {
-            mWebView.onTrackballEvent(event);
-            // always return true as we are the handler
-            return true;
-        }
-    }
-    
-    private final WebChromeClient.CustomViewCallback mCallback =
-        new WebChromeClient.CustomViewCallback() {
-            public void onCustomViewHidden() {
-
-                mWebView.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
-                    .sendToTarget();
-
-                mWebView.getWebViewCore().sendMessage(
-                        WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
-
-                mLayout.removeView(mContentView);
-                mLayout = null;
-
-                // Re enable plugin views.
-                mWebView.getViewManager().showAll();
-            }
-        };
-}
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
deleted file mode 100644
index fe40156..0000000
--- a/core/java/android/webkit/PluginManager.java
+++ /dev/null
@@ -1,300 +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.
- */
-
-package android.webkit;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.SystemProperties;
-import android.util.Log;
-
-/**
- * Class for managing the relationship between the {@link WebViewClassic} and installed
- * plugins in the system. You can find this class through
- * {@link PluginManager#getInstance}.
- * 
- * @hide pending API solidification
- */
-public class PluginManager {
-
-    /**
-     * Service Action: A plugin wishes to be loaded in the WebView must provide
-     * {@link android.content.IntentFilter IntentFilter} that accepts this
-     * action in their AndroidManifest.xml.
-     * <p>
-     * TODO: we may change this to a new PLUGIN_ACTION if this is going to be
-     * public.
-     */
-    @SdkConstant(SdkConstantType.SERVICE_ACTION)
-    public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
-
-    /**
-     * A plugin wishes to be loaded in the WebView must provide this permission
-     * in their AndroidManifest.xml.
-     */
-    public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
-
-    private static final String LOGTAG = "PluginManager";
-
-    private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
-
-    private static final String PLUGIN_TYPE = "type";
-    private static final String TYPE_NATIVE = "native";
-
-    private static PluginManager mInstance = null;
-
-    private final Context mContext;
-
-    private ArrayList<PackageInfo> mPackageInfoCache;
-
-    // Only plugin matches one of the signatures in the list can be loaded
-    // inside the WebView process
-    private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637";
-
-    private static final Signature[] SIGNATURES = new Signature[] {
-        new Signature(SIGNATURE_1)
-    };
-
-    private PluginManager(Context context) {
-        mContext = context;
-        mPackageInfoCache = new ArrayList<PackageInfo>();
-    }
-
-    public static synchronized PluginManager getInstance(Context context) {
-        if (mInstance == null) {
-            if (context == null) {
-                throw new IllegalStateException(
-                        "First call to PluginManager need a valid context.");
-            }
-            mInstance = new PluginManager(context.getApplicationContext());
-        }
-        return mInstance;
-    }
-
-    /**
-     * Signal the WebCore thread to refresh its list of plugins. Use this if the
-     * directory contents of one of the plugin directories has been modified and
-     * needs its changes reflecting. May cause plugin load and/or unload.
-     * 
-     * @param reloadOpenPages Set to true to reload all open pages.
-     */
-    public void refreshPlugins(boolean reloadOpenPages) {
-        BrowserFrame.sJavaBridge.obtainMessage(
-                JWebCoreJavaBridge.REFRESH_PLUGINS, reloadOpenPages)
-                .sendToTarget();
-    }
-
-    String[] getPluginDirectories() {
-
-        ArrayList<String> directories = new ArrayList<String>();
-        PackageManager pm = mContext.getPackageManager();
-        List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
-
-        synchronized(mPackageInfoCache) {
-
-            // clear the list of existing packageInfo objects
-            mPackageInfoCache.clear();
-
-            for (ResolveInfo info : plugins) {
-
-                // retrieve the plugin's service information
-                ServiceInfo serviceInfo = info.serviceInfo;
-                if (serviceInfo == null) {
-                    Log.w(LOGTAG, "Ignore bad plugin");
-                    continue;
-                }
-
-                // retrieve information from the plugin's manifest
-                PackageInfo pkgInfo;
-                try {
-                    pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
-                                    PackageManager.GET_PERMISSIONS
-                                    | PackageManager.GET_SIGNATURES);
-                } catch (NameNotFoundException e) {
-                    Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
-                    continue;
-                }
-                if (pkgInfo == null) {
-                    continue;
-                }
-
-                /*
-                 * find the location of the plugin's shared library. The default
-                 * is to assume the app is either a user installed app or an
-                 * updated system app. In both of these cases the library is
-                 * stored in the app's data directory.
-                 */
-                String directory = pkgInfo.applicationInfo.dataDir + "/lib";
-                final int appFlags = pkgInfo.applicationInfo.flags;
-                final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
-                                               ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-                // preloaded system app with no user updates
-                if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
-                    directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
-                }
-
-                // check if the plugin has the required permissions and
-                // signatures
-                if (!containsPluginPermissionAndSignatures(pkgInfo)) {
-                    continue;
-                }
-
-                // determine the type of plugin from the manifest
-                if (serviceInfo.metaData == null) {
-                    Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
-                    continue;
-                }
-
-                String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
-                if (!TYPE_NATIVE.equals(pluginType)) {
-                    Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
-                    continue;
-                }
-
-                try {
-                    Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
-
-                    //TODO implement any requirements of the plugin class here!
-                    boolean classFound = true;
-
-                    if (!classFound) {
-                        Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
-                        continue;
-                    }
-
-                } catch (NameNotFoundException e) {
-                    Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
-                    continue;
-                } catch (ClassNotFoundException e) {
-                    Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
-                    continue;
-                }
-
-                // if all checks have passed then make the plugin available
-                mPackageInfoCache.add(pkgInfo);
-                directories.add(directory);
-            }
-        }
-
-        return directories.toArray(new String[directories.size()]);
-    }
-
-    /* package */
-    boolean containsPluginPermissionAndSignatures(String pluginAPKName) {
-        PackageManager pm = mContext.getPackageManager();
-
-        // retrieve information from the plugin's manifest
-        try {
-            PackageInfo pkgInfo = pm.getPackageInfo(pluginAPKName, PackageManager.GET_PERMISSIONS
-                    | PackageManager.GET_SIGNATURES);
-            if (pkgInfo != null) {
-                return containsPluginPermissionAndSignatures(pkgInfo);
-            }
-        } catch (NameNotFoundException e) {
-            Log.w(LOGTAG, "Can't find plugin: " + pluginAPKName);
-        }
-        return false;
-    }
-
-    private static boolean containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
-
-        // check if the plugin has the required permissions
-        String permissions[] = pkgInfo.requestedPermissions;
-        if (permissions == null) {
-            return false;
-        }
-        boolean permissionOk = false;
-        for (String permit : permissions) {
-            if (PLUGIN_PERMISSION.equals(permit)) {
-                permissionOk = true;
-                break;
-            }
-        }
-        if (!permissionOk) {
-            return false;
-        }
-
-        // check to ensure the plugin is properly signed
-        Signature signatures[] = pkgInfo.signatures;
-        if (signatures == null) {
-            return false;
-        }
-        if (SystemProperties.getBoolean("ro.secure", false)) {
-            boolean signatureMatch = false;
-            for (Signature signature : signatures) {
-                for (int i = 0; i < SIGNATURES.length; i++) {
-                    if (SIGNATURES[i].equals(signature)) {
-                        signatureMatch = true;
-                        break;
-                    }
-                }
-            }
-            if (!signatureMatch) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /* package */
-    String getPluginsAPKName(String pluginLib) {
-
-        // basic error checking on input params
-        if (pluginLib == null || pluginLib.length() == 0) {
-            return null;
-        }
-
-        // must be synchronized to ensure the consistency of the cache
-        synchronized(mPackageInfoCache) {
-            for (PackageInfo pkgInfo : mPackageInfoCache) {
-                if (pluginLib.contains(pkgInfo.packageName)) {
-                    return pkgInfo.packageName;
-                }
-            }
-        }
-
-        // if no apk was found then return null
-        return null;
-    }
-
-    String getPluginSharedDataDirectory() {
-        return mContext.getDir("plugins", 0).getPath();
-    }
-
-    /* package */
-    Class<?> getPluginClass(String packageName, String className)
-            throws NameNotFoundException, ClassNotFoundException {
-        Context pluginContext = mContext.createPackageContext(packageName,
-                Context.CONTEXT_INCLUDE_CODE |
-                Context.CONTEXT_IGNORE_SECURITY);
-        ClassLoader pluginCL = pluginContext.getClassLoader();
-        return pluginCL.loadClass(className);
-    }
-}
diff --git a/core/java/android/webkit/QuadF.java b/core/java/android/webkit/QuadF.java
deleted file mode 100644
index e9011e3..0000000
--- a/core/java/android/webkit/QuadF.java
+++ /dev/null
@@ -1,85 +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;
-
-import android.graphics.PointF;
-
-/**
- * A quadrilateral, determined by four points, clockwise order. Typically
- * p1 is "top-left" and p4 is "bottom-left" following webkit's rectangle-to-
- * FloatQuad conversion.
- */
-class QuadF {
-    public PointF p1;
-    public PointF p2;
-    public PointF p3;
-    public PointF p4;
-
-    public QuadF() {
-        p1 = new PointF();
-        p2 = new PointF();
-        p3 = new PointF();
-        p4 = new PointF();
-    }
-
-    public void offset(float dx, float dy) {
-        p1.offset(dx, dy);
-        p2.offset(dx, dy);
-        p3.offset(dx, dy);
-        p4.offset(dx, dy);
-    }
-
-    /**
-     * Determines if the quadrilateral contains the given point. This does
-     * not work if the quadrilateral is self-intersecting or if any inner
-     * angle is reflex (greater than 180 degrees).
-     */
-    public boolean containsPoint(float x, float y) {
-        return isPointInTriangle(x, y, p1, p2, p3) ||
-                isPointInTriangle(x, y, p1, p3, p4);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder s = new StringBuilder("QuadF(");
-        s.append(p1.x).append(",").append(p1.y);
-        s.append(" - ");
-        s.append(p2.x).append(",").append(p2.y);
-        s.append(" - ");
-        s.append(p3.x).append(",").append(p3.y);
-        s.append(" - ");
-        s.append(p4.x).append(",").append(p4.y);
-        s.append(")");
-        return s.toString();
-    }
-
-    private static boolean isPointInTriangle(float x0, float y0,
-            PointF r1, PointF r2, PointF r3) {
-        // Use the barycentric technique
-        float x13 = r1.x - r3.x;
-        float y13 = r1.y - r3.y;
-        float x23 = r2.x - r3.x;
-        float y23 = r2.y - r3.y;
-        float x03 = x0 - r3.x;
-        float y03 = y0 - r3.y;
-
-        float determinant = (y23 * x13) - (x23 * y13);
-        float lambda1 = ((y23 * x03) - (x23 * y03))/determinant;
-        float lambda2 = ((x13 * y03) - (y13 * x03))/determinant;
-        float lambda3 = 1 - lambda1 - lambda2;
-        return lambda1 >= 0.0f && lambda2 >= 0.0f && lambda3 >= 0.0f;
-    }
-}
diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java
deleted file mode 100644
index f9f5b03..0000000
--- a/core/java/android/webkit/SelectActionModeCallback.java
+++ /dev/null
@@ -1,149 +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.
- */
-
-package android.webkit;
-
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Browser;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-
-class SelectActionModeCallback implements ActionMode.Callback {
-    private WebViewClassic mWebView;
-    private ActionMode mActionMode;
-    private boolean mIsTextSelected = true;
-
-    void setWebView(WebViewClassic webView) {
-        mWebView = webView;
-    }
-
-    void setTextSelected(boolean isTextSelected) {
-        mIsTextSelected = isTextSelected;
-    }
-
-    void finish() {
-        // It is possible that onCreateActionMode was never called, in the case
-        // where there is no ActionBar, for example.
-        if (mActionMode != null) {
-            mActionMode.finish();
-        }
-    }
-
-    // ActionMode.Callback implementation
-
-    @Override
-    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-        mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy, menu);
-
-        final Context context = mWebView.getContext();
-        mode.setTitle(context.getString(com.android.internal.R.string.textSelectionCABTitle));
-        mode.setTitleOptionalHint(true);
-
-        // If the action mode UI we're running in isn't capable of taking window focus
-        // the user won't be able to type into the find on page UI. Disable this functionality.
-        // (Note that this should only happen in floating dialog windows.)
-        // This can be removed once we can handle multiple focusable windows at a time
-        // in a better way.
-        ClipboardManager cm = (ClipboardManager)(context
-                .getSystemService(Context.CLIPBOARD_SERVICE));
-        boolean isFocusable = mode.isUiFocusable();
-        boolean isEditable = mWebView.focusCandidateIsEditableText();
-        boolean canPaste = isEditable && cm.hasPrimaryClip() && isFocusable;
-        boolean canFind = !isEditable && isFocusable;
-        boolean canCut = isEditable && mIsTextSelected && isFocusable;
-        boolean canCopy = mIsTextSelected;
-        boolean canWebSearch = mIsTextSelected;
-        setMenuVisibility(menu, canFind, com.android.internal.R.id.find);
-        setMenuVisibility(menu, canPaste, com.android.internal.R.id.paste);
-        setMenuVisibility(menu, canCut, com.android.internal.R.id.cut);
-        setMenuVisibility(menu, canCopy, com.android.internal.R.id.copy);
-        setMenuVisibility(menu, canWebSearch, com.android.internal.R.id.websearch);
-        mActionMode = mode;
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-        return true;
-    }
-
-    @Override
-    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-        switch(item.getItemId()) {
-            case android.R.id.cut:
-                mWebView.cutSelection();
-                mode.finish();
-                break;
-
-            case android.R.id.copy:
-                mWebView.copySelection();
-                mode.finish();
-                break;
-
-            case android.R.id.paste:
-                mWebView.pasteFromClipboard();
-                mode.finish();
-                break;
-
-            case com.android.internal.R.id.share:
-                String selection = mWebView.getSelection();
-                Browser.sendString(mWebView.getContext(), selection);
-                mode.finish();
-                break;
-
-            case com.android.internal.R.id.select_all:
-                mWebView.selectAll();
-                break;
-
-            case com.android.internal.R.id.find:
-                String sel= mWebView.getSelection();
-                mode.finish();
-                mWebView.showFindDialog(sel, false);
-                break;
-            case com.android.internal.R.id.websearch:
-                mode.finish();
-                Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
-                i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
-                i.putExtra(SearchManager.QUERY, mWebView.getSelection());
-                if (!(mWebView.getContext() instanceof Activity)) {
-                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                }
-                mWebView.getContext().startActivity(i);
-                break;
-
-            default:
-                return false;
-        }
-        return true;
-    }
-
-    @Override
-    public void onDestroyActionMode(ActionMode mode) {
-        mWebView.selectionDone();
-    }
-
-    private void setMenuVisibility(Menu menu, boolean visible, int resourceId) {
-        final MenuItem item = menu.findItem(resourceId);
-        if (item != null) {
-            item.setVisible(visible);
-        }
-    }
-}
diff --git a/core/java/android/webkit/SslCertLookupTable.java b/core/java/android/webkit/SslCertLookupTable.java
deleted file mode 100644
index 98ace4f..0000000
--- a/core/java/android/webkit/SslCertLookupTable.java
+++ /dev/null
@@ -1,72 +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.
- */
-
-package android.webkit;
-
-import android.os.Bundle;
-import android.net.http.SslError;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * Stores the user's decision of whether to allow or deny an invalid certificate.
- *
- * This class is not threadsafe. It is used only on the WebCore thread. Also, it
- * is used only by the Chromium HTTP stack.
- */
-final class SslCertLookupTable {
-    private static SslCertLookupTable sTable;
-    // We store the most severe error we're willing to allow for each host.
-    private final Bundle table;
-
-    public static SslCertLookupTable getInstance() {
-        if (sTable == null) {
-            sTable = new SslCertLookupTable();
-        }
-        return sTable;
-    }
-
-    private SslCertLookupTable() {
-        table = new Bundle();
-    }
-
-    public void setIsAllowed(SslError sslError) {
-        String host;
-        try {
-            host = new URL(sslError.getUrl()).getHost();
-        } catch(MalformedURLException e) {
-            return;
-        }
-        table.putInt(host, sslError.getPrimaryError());
-    }
-
-    // We allow the decision to be re-used if it's for the same host and is for
-    // an error of equal or greater severity than this error.
-    public boolean isAllowed(SslError sslError) {
-        String host;
-        try {
-            host = new URL(sslError.getUrl()).getHost();
-        } catch(MalformedURLException e) {
-            return false;
-        }
-        return table.containsKey(host) && sslError.getPrimaryError() <= table.getInt(host);
-    }
-
-    public void clear() {
-        table.clear();
-    }
-}
diff --git a/core/java/android/webkit/SslClientCertLookupTable.java b/core/java/android/webkit/SslClientCertLookupTable.java
deleted file mode 100644
index c52b7e8..0000000
--- a/core/java/android/webkit/SslClientCertLookupTable.java
+++ /dev/null
@@ -1,74 +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.
- */
-
-package android.webkit;
-
-import java.security.PrivateKey;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A simple class to store client certificates that user has chosen.
- */
-final class SslClientCertLookupTable {
-    private static SslClientCertLookupTable sTable;
-    private final Map<String, PrivateKey> privateKeys;
-    private final Map<String, byte[][]> certificateChains;
-    private final Set<String> denied;
-
-    public static synchronized SslClientCertLookupTable getInstance() {
-        if (sTable == null) {
-            sTable = new SslClientCertLookupTable();
-        }
-        return sTable;
-    }
-
-    private SslClientCertLookupTable() {
-        privateKeys = new HashMap<String, PrivateKey>();
-        certificateChains = new HashMap<String, byte[][]>();
-        denied = new HashSet<String>();
-    }
-
-    public void Allow(String host_and_port, PrivateKey privateKey, byte[][] chain) {
-        privateKeys.put(host_and_port, privateKey);
-        certificateChains.put(host_and_port, chain);
-        denied.remove(host_and_port);
-    }
-
-    public void Deny(String host_and_port) {
-        privateKeys.remove(host_and_port);
-        certificateChains.remove(host_and_port);
-        denied.add(host_and_port);
-    }
-
-    public boolean IsAllowed(String host_and_port) {
-        return privateKeys.containsKey(host_and_port);
-    }
-
-    public boolean IsDenied(String host_and_port) {
-        return denied.contains(host_and_port);
-    }
-
-    public PrivateKey PrivateKey(String host_and_port) {
-        return privateKeys.get(host_and_port);
-    }
-
-    public byte[][] CertificateChain(String host_and_port) {
-        return certificateChains.get(host_and_port);
-    }
-}
diff --git a/core/java/android/webkit/ViewManager.java b/core/java/android/webkit/ViewManager.java
deleted file mode 100644
index 34065a1..0000000
--- a/core/java/android/webkit/ViewManager.java
+++ /dev/null
@@ -1,296 +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.
- */
-
-package android.webkit;
-
-import android.util.DisplayMetrics;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsoluteLayout;
-
-import java.util.ArrayList;
-
-class ViewManager {
-    private final WebViewClassic mWebView;
-    private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
-    private boolean mHidden;
-    private boolean mReadyToDraw;
-    private boolean mZoomInProgress = false;
-
-    // Threshold at which a surface is prevented from further increasing in size
-    private final int MAX_SURFACE_AREA;
-    // GPU Limit (hard coded for now)
-    private static final int MAX_SURFACE_DIMENSION = 2048;
-
-    class ChildView {
-        int x;
-        int y;
-        int width;
-        int height;
-        View mView; // generic view to show
-
-        ChildView() {
-        }
-
-        void setBounds(int x, int y, int width, int height) {
-            this.x = x;
-            this.y = y;
-            this.width = width;
-            this.height = height;
-        }
-
-        void attachView(int x, int y, int width, int height) {
-            if (mView == null) {
-                return;
-            }
-            setBounds(x, y, width, height);
-
-            mWebView.mPrivateHandler.post(new Runnable() {
-                public void run() {
-                    // This method may be called multiple times. If the view is
-                    // already attached, just set the new LayoutParams,
-                    // otherwise attach the view and add it to the list of
-                    // children.
-                    requestLayout(ChildView.this);
-
-                    if (mView.getParent() == null) {
-                        attachViewOnUIThread();
-                    }
-                }
-            });
-        }
-
-        private void attachViewOnUIThread() {
-            mWebView.getWebView().addView(mView);
-            mChildren.add(this);
-            if (!mReadyToDraw) {
-                mView.setVisibility(View.GONE);
-            }
-        }
-
-        void removeView() {
-            if (mView == null) {
-                return;
-            }
-            mWebView.mPrivateHandler.post(new Runnable() {
-                public void run() {
-                    removeViewOnUIThread();
-                }
-            });
-        }
-
-        private void removeViewOnUIThread() {
-            mWebView.getWebView().removeView(mView);
-            mChildren.remove(this);
-        }
-    }
-
-    ViewManager(WebViewClassic w) {
-        mWebView = w;
-        DisplayMetrics metrics = w.getWebView().getResources().getDisplayMetrics();
-        int pixelArea = metrics.widthPixels * metrics.heightPixels;
-        /* set the threshold to be 275% larger than the screen size. The
-           percentage is simply an estimation and is not based on anything but
-           basic trial-and-error tests run on multiple devices.
-         */
-        MAX_SURFACE_AREA = (int)(pixelArea * 2.75);
-    }
-
-    ChildView createView() {
-        return new ChildView();
-    }
-
-    /**
-     * This should only be called from the UI thread.
-     */
-    private void requestLayout(ChildView v) {
-
-        int width = mWebView.contentToViewDimension(v.width);
-        int height = mWebView.contentToViewDimension(v.height);
-        int x = mWebView.contentToViewX(v.x);
-        int y = mWebView.contentToViewY(v.y);
-
-        AbsoluteLayout.LayoutParams lp;
-        ViewGroup.LayoutParams layoutParams = v.mView.getLayoutParams();
-
-        if (layoutParams instanceof AbsoluteLayout.LayoutParams) {
-            lp = (AbsoluteLayout.LayoutParams) layoutParams;
-            lp.width = width;
-            lp.height = height;
-            lp.x = x;
-            lp.y = y;
-        } else {
-            lp = new AbsoluteLayout.LayoutParams(width, height, x, y);
-        }
-
-        // apply the layout to the view
-        v.mView.setLayoutParams(lp);
-
-        if(v.mView instanceof SurfaceView) {
-
-            final SurfaceView sView = (SurfaceView) v.mView;
-
-            if (sView.isFixedSize() && mZoomInProgress) {
-                /* If we're already fixed, and we're in a zoom, then do nothing
-                   about the size. Just wait until we get called at the end of
-                   the zoom session (with mZoomInProgress false) and we'll
-                   fixup our size then.
-                 */
-                return;
-            }
-
-            /* Compute proportional fixed width/height if necessary.
-             *
-             * NOTE: plugins (e.g. Flash) must not explicitly fix the size of
-             * their surface. The logic below will result in unexpected behavior
-             * for the plugin if they attempt to fix the size of the surface.
-             */
-            int fixedW = width;
-            int fixedH = height;
-            if (fixedW > MAX_SURFACE_DIMENSION || fixedH > MAX_SURFACE_DIMENSION) {
-                if (v.width > v.height) {
-                    fixedW = MAX_SURFACE_DIMENSION;
-                    fixedH = v.height * MAX_SURFACE_DIMENSION / v.width;
-                } else {
-                    fixedH = MAX_SURFACE_DIMENSION;
-                    fixedW = v.width * MAX_SURFACE_DIMENSION / v.height;
-                }
-            }
-            if (fixedW * fixedH > MAX_SURFACE_AREA) {
-                float area = MAX_SURFACE_AREA;
-                if (v.width > v.height) {
-                    fixedW = (int)Math.sqrt(area * v.width / v.height);
-                    fixedH = v.height * fixedW / v.width;
-                } else {
-                    fixedH = (int)Math.sqrt(area * v.height / v.width);
-                    fixedW = v.width * fixedH / v.height;
-                }
-            }
-
-            if (fixedW != width || fixedH != height) {
-                // if we get here, either our dimensions or area (or both)
-                // exeeded our max, so we had to compute fixedW and fixedH
-                sView.getHolder().setFixedSize(fixedW, fixedH);
-            } else if (!sView.isFixedSize() && mZoomInProgress) {
-                // just freeze where we were (view size) until we're done with
-                // the zoom progress
-                sView.getHolder().setFixedSize(sView.getWidth(),
-                                               sView.getHeight());
-            } else if (sView.isFixedSize() && !mZoomInProgress) {
-                /* The changing of visibility is a hack to get around a bug in
-                 * the framework that causes the surface to revert to the size
-                 * it was prior to being fixed before it redraws using the
-                 * values currently in its layout.
-                 *
-                 * The surface is destroyed when it is set to invisible and then
-                 * recreated at the new dimensions when it is made visible. The
-                 * same destroy/create step occurs without the change in
-                 * visibility, but then exhibits the behavior described in the
-                 * previous paragraph.
-                 */
-                if (sView.getVisibility() == View.VISIBLE) {
-                    sView.setVisibility(View.INVISIBLE);
-                    sView.getHolder().setSizeFromLayout();
-                    // setLayoutParams() only requests the layout. If we set it
-                    // to VISIBLE now, it will use the old dimension to set the
-                    // size. Post a message to ensure that it shows the new size.
-                    mWebView.mPrivateHandler.post(new Runnable() {
-                        public void run() {
-                            sView.setVisibility(View.VISIBLE);
-                        }
-                    });
-                } else {
-                    sView.getHolder().setSizeFromLayout();
-                }
-            }
-        }
-    }
-
-    void startZoom() {
-        mZoomInProgress = true;
-        for (ChildView v : mChildren) {
-            requestLayout(v);
-        }
-    }
-
-    void endZoom() {
-        mZoomInProgress = false;
-        for (ChildView v : mChildren) {
-            requestLayout(v);
-        }
-    }
-
-    void scaleAll() {
-        for (ChildView v : mChildren) {
-            requestLayout(v);
-        }
-    }
-
-    void hideAll() {
-        if (mHidden) {
-            return;
-        }
-        for (ChildView v : mChildren) {
-            v.mView.setVisibility(View.GONE);
-        }
-        mHidden = true;
-    }
-
-    void showAll() {
-        if (!mHidden) {
-            return;
-        }
-        for (ChildView v : mChildren) {
-            v.mView.setVisibility(View.VISIBLE);
-        }
-        mHidden = false;
-    }
-
-    void postResetStateAll() {
-        mWebView.mPrivateHandler.post(new Runnable() {
-            public void run() {
-                mReadyToDraw = false;
-            }
-        });
-    }
-
-    void postReadyToDrawAll() {
-        mWebView.mPrivateHandler.post(new Runnable() {
-            public void run() {
-                mReadyToDraw = true;
-                for (ChildView v : mChildren) {
-                    v.mView.setVisibility(View.VISIBLE);
-                }
-            }
-        });
-    }
-
-    ChildView hitTest(int contentX, int contentY) {
-        if (mHidden) {
-            return null;
-        }
-        for (ChildView v : mChildren) {
-            if (v.mView.getVisibility() == View.VISIBLE) {
-                if (contentX >= v.x && contentX < (v.x + v.width)
-                        && contentY >= v.y && contentY < (v.y + v.height)) {
-                    return v;
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
deleted file mode 100644
index 1d44b96..0000000
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ /dev/null
@@ -1,88 +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.
- */
-package android.webkit;
-
-import android.graphics.Point;
-import android.webkit.WebViewCore.DrawData;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @hide
- */
-class ViewStateSerializer {
-
-    private static final int WORKING_STREAM_STORAGE = 16 * 1024;
-
-    // VERSION = 1 was for pictures encoded using a previous copy of libskia
-    static final int VERSION = 2;
-
-    static boolean serializeViewState(OutputStream stream, DrawData draw)
-            throws IOException {
-        int baseLayer = draw.mBaseLayer;
-        if (baseLayer == 0) {
-            return false;
-        }
-        DataOutputStream dos = new DataOutputStream(stream);
-        dos.writeInt(VERSION);
-        dos.writeInt(draw.mContentSize.x);
-        dos.writeInt(draw.mContentSize.y);
-        return nativeSerializeViewState(baseLayer, dos,
-                new byte[WORKING_STREAM_STORAGE]);
-    }
-
-    static DrawData deserializeViewState(InputStream stream)
-            throws IOException {
-        DataInputStream dis = new DataInputStream(stream);
-        int version = dis.readInt();
-        if (version > VERSION) {
-            throw new IOException("Unexpected version: " + version);
-        }
-        int contentWidth = dis.readInt();
-        int contentHeight = dis.readInt();
-        int baseLayer = nativeDeserializeViewState(version, dis,
-                new byte[WORKING_STREAM_STORAGE]);
-
-        final WebViewCore.DrawData draw = new WebViewCore.DrawData();
-        draw.mViewState = new WebViewCore.ViewState();
-        draw.mContentSize = new Point(contentWidth, contentHeight);
-        draw.mBaseLayer = baseLayer;
-        stream.close();
-        return draw;
-    }
-
-    public static void dumpLayerHierarchy(int baseLayer, OutputStream out, int level) {
-        nativeDumpLayerHierarchy(baseLayer, level, out,
-                new byte[WORKING_STREAM_STORAGE]);
-    }
-
-
-    private static native void nativeDumpLayerHierarchy(int baseLayer, int level,
-            OutputStream out, byte[] storage);
-
-    private static native boolean nativeSerializeViewState(int baseLayer,
-            OutputStream stream, byte[] storage);
-
-    // Returns a pointer to the BaseLayer
-    private static native int nativeDeserializeViewState(int version,
-            InputStream stream, byte[] storage);
-
-    private ViewStateSerializer() {}
-}
diff --git a/core/java/android/webkit/WebBackForwardListClassic.java b/core/java/android/webkit/WebBackForwardListClassic.java
deleted file mode 100644
index 2a14e6b..0000000
--- a/core/java/android/webkit/WebBackForwardListClassic.java
+++ /dev/null
@@ -1,166 +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;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-
-/* package */ class WebBackForwardListClassic extends WebBackForwardList implements Cloneable,
-        Serializable {
-
-    // Current position in the list.
-    private int mCurrentIndex;
-    // ArrayList of WebHistoryItems for maintaining our copy.
-    private ArrayList<WebHistoryItemClassic> mArray;
-    // Flag to indicate that the list is invalid
-    private boolean mClearPending;
-    // CallbackProxy to issue client callbacks.
-    private final CallbackProxy mCallbackProxy;
-
-    /*package*/ WebBackForwardListClassic(CallbackProxy proxy) {
-        mCurrentIndex = -1;
-        mArray = new ArrayList<WebHistoryItemClassic>();
-        mCallbackProxy = proxy;
-    }
-
-    public synchronized WebHistoryItemClassic getCurrentItem() {
-        return getItemAtIndex(mCurrentIndex);
-    }
-
-    public synchronized int getCurrentIndex() {
-        return mCurrentIndex;
-    }
-
-    public synchronized WebHistoryItemClassic getItemAtIndex(int index) {
-        if (index < 0 || index >= getSize()) {
-            return null;
-        }
-        return mArray.get(index);
-    }
-
-    public synchronized int getSize() {
-        return mArray.size();
-    }
-
-    /**
-     * Mark the back/forward list as having a pending clear. This is used on the
-     * UI side to mark the list as being invalid during the clearHistory method.
-     */
-    /*package*/ synchronized void setClearPending() {
-        mClearPending = true;
-    }
-
-    /**
-     * Return the status of the clear flag. This is used on the UI side to
-     * determine if the list is valid for checking things like canGoBack.
-     */
-    /*package*/ synchronized boolean getClearPending() {
-        return mClearPending;
-    }
-
-    /**
-     * Add a new history item to the list. This will remove all items after the
-     * current item and append the new item to the end of the list. Called from
-     * the WebCore thread only. Synchronized because the UI thread may be
-     * reading the array or the current index.
-     * @param item A new history item.
-     */
-    /*package*/ synchronized void addHistoryItem(WebHistoryItem item) {
-        // Update the current position because we are going to add the new item
-        // in that slot.
-        ++mCurrentIndex;
-        // If the current position is not at the end, remove all history items
-        // after the current item.
-        final int size = mArray.size();
-        final int newPos = mCurrentIndex;
-        if (newPos != size) {
-            for (int i = size - 1; i >= newPos; i--) {
-                final WebHistoryItem h = mArray.remove(i);
-            }
-        }
-        // Add the item to the list.
-        mArray.add((WebHistoryItemClassic) item);
-        if (mCallbackProxy != null) {
-            mCallbackProxy.onNewHistoryItem(item);
-        }
-    }
-
-    /**
-     * Clear the back/forward list. Called from the WebCore thread.
-     */
-    /*package*/ synchronized void close(int nativeFrame) {
-        // Clear the array first because nativeClose will call addHistoryItem
-        // with the current item.
-        mArray.clear();
-        mCurrentIndex = -1;
-        nativeClose(nativeFrame);
-        // Reset the clear flag
-        mClearPending = false;
-    }
-
-    /* Remove the item at the given index. Called by JNI only. */
-    private synchronized void removeHistoryItem(int index) {
-        // XXX: This is a special case. Since the callback is only triggered
-        // when removing the first item, we can assert that the index is 0.
-        // This lets us change the current index without having to query the
-        // native BackForwardList.
-        if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) {
-            throw new AssertionError();
-        }
-        final WebHistoryItem h = mArray.remove(index);
-        // XXX: If we ever add another callback for removing history items at
-        // any index, this will no longer be valid.
-        mCurrentIndex--;
-    }
-
-    public synchronized WebBackForwardListClassic clone() {
-        WebBackForwardListClassic l = new WebBackForwardListClassic(null);
-        if (mClearPending) {
-            // If a clear is pending, return a copy with only the current item.
-            l.addHistoryItem(getCurrentItem());
-            return l;
-        }
-        l.mCurrentIndex = mCurrentIndex;
-        int size = getSize();
-        l.mArray = new ArrayList<WebHistoryItemClassic>(size);
-        for (int i = 0; i < size; i++) {
-            // Add a copy of each WebHistoryItem
-            l.mArray.add(mArray.get(i).clone());
-        }
-        return l;
-    }
-
-    /**
-     * Set the new history index.
-     * @param newIndex The new history index.
-     */
-    /*package*/ synchronized void setCurrentIndex(int newIndex) {
-        mCurrentIndex = newIndex;
-        if (mCallbackProxy != null) {
-            mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex);
-        }
-    }
-
-    /**
-     * Restore the history index.
-     */
-    /*package*/ static native synchronized void restoreIndex(int nativeFrame,
-            int index);
-
-    /* Close the native list. */
-    private static native void nativeClose(int nativeFrame);
-}
diff --git a/core/java/android/webkit/WebCoreThreadWatchdog.java b/core/java/android/webkit/WebCoreThreadWatchdog.java
deleted file mode 100644
index c27bb5f..0000000
--- a/core/java/android/webkit/WebCoreThreadWatchdog.java
+++ /dev/null
@@ -1,277 +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;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.webkit.WebViewCore.EventHub;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-// A Runnable that will monitor if the WebCore thread is still
-// processing messages by pinging it every so often. It is safe
-// to call the public methods of this class from any thread.
-class WebCoreThreadWatchdog implements Runnable {
-
-    // A message with this id is sent by the WebCore thread to notify the
-    // Watchdog that the WebCore thread is still processing messages
-    // (i.e. everything is OK).
-    private static final int IS_ALIVE = 100;
-
-    // This message is placed in the Watchdog's queue and removed when we
-    // receive an IS_ALIVE. If it is ever processed, we consider the
-    // WebCore thread unresponsive.
-    private static final int TIMED_OUT = 101;
-
-    // Wait 10s after hearing back from the WebCore thread before checking it's still alive.
-    private static final int HEARTBEAT_PERIOD = 10 * 1000;
-
-    // If there's no callback from the WebCore thread for 30s, prompt the user the page has
-    // become unresponsive.
-    private static final int TIMEOUT_PERIOD = 30 * 1000;
-
-    // After the first timeout, use a shorter period before re-prompting the user.
-    private static final int SUBSEQUENT_TIMEOUT_PERIOD = 15 * 1000;
-
-    private Handler mWebCoreThreadHandler;
-    private Handler mHandler;
-    private boolean mPaused;
-
-    private Set<WebViewClassic> mWebViews;
-
-    private static WebCoreThreadWatchdog sInstance;
-
-    public synchronized static WebCoreThreadWatchdog start(Handler webCoreThreadHandler) {
-        if (sInstance == null) {
-            sInstance = new WebCoreThreadWatchdog(webCoreThreadHandler);
-            new Thread(sInstance, "WebCoreThreadWatchdog").start();
-        }
-        return sInstance;
-    }
-
-    public synchronized static void registerWebView(WebViewClassic w) {
-        if (sInstance != null) {
-            sInstance.addWebView(w);
-        }
-    }
-
-    public synchronized static void unregisterWebView(WebViewClassic w) {
-        if (sInstance != null) {
-            sInstance.removeWebView(w);
-        }
-    }
-
-    public synchronized static void pause() {
-        if (sInstance != null) {
-            sInstance.pauseWatchdog();
-        }
-    }
-
-    public synchronized static void resume() {
-        if (sInstance != null) {
-            sInstance.resumeWatchdog();
-        }
-    }
-
-    private void addWebView(WebViewClassic w) {
-        if (mWebViews == null) {
-            mWebViews = new HashSet<WebViewClassic>();
-        }
-        mWebViews.add(w);
-    }
-
-    private void removeWebView(WebViewClassic w) {
-        mWebViews.remove(w);
-    }
-
-    private WebCoreThreadWatchdog(Handler webCoreThreadHandler) {
-        mWebCoreThreadHandler = webCoreThreadHandler;
-    }
-
-    private void pauseWatchdog() {
-        mPaused = true;
-
-        if (mHandler == null) {
-            return;
-        }
-
-        mHandler.removeMessages(TIMED_OUT);
-        mHandler.removeMessages(IS_ALIVE);
-        mWebCoreThreadHandler.removeMessages(EventHub.HEARTBEAT);
-    }
-
-    private void resumeWatchdog() {
-        if (!mPaused) {
-            // Do nothing if we get a call to resume without being paused.
-            // This can happen during the initialisation of the WebView.
-            return;
-        }
-
-        mPaused = false;
-
-        if (mHandler == null) {
-            return;
-        }
-
-        mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
-                mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
-    }
-
-    private void createHandler() {
-        synchronized (WebCoreThreadWatchdog.class) {
-            mHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                    case IS_ALIVE:
-                        synchronized(WebCoreThreadWatchdog.class) {
-                            if (mPaused) {
-                                return;
-                            }
-
-                            // The WebCore thread still seems alive. Reset the countdown timer.
-                            removeMessages(TIMED_OUT);
-                            sendMessageDelayed(obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
-                            mWebCoreThreadHandler.sendMessageDelayed(
-                                    mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
-                                            mHandler.obtainMessage(IS_ALIVE)),
-                                    HEARTBEAT_PERIOD);
-                        }
-                        break;
-
-                    case TIMED_OUT:
-                        boolean postedDialog = false;
-                        synchronized (WebCoreThreadWatchdog.class) {
-                            Iterator<WebViewClassic> it = mWebViews.iterator();
-                            // Check each WebView we are aware of and find one that is capable of
-                            // showing the user a prompt dialog.
-                            while (it.hasNext()) {
-                                WebView activeView = it.next().getWebView();
-
-                                if (activeView.getWindowToken() != null &&
-                                        activeView.getViewRootImpl() != null) {
-                                    postedDialog = activeView.post(new PageNotRespondingRunnable(
-                                            activeView.getContext(), this));
-
-                                    if (postedDialog) {
-                                        // We placed the message into the UI thread for an attached
-                                        // WebView so we've made our best attempt to display the
-                                        // "page not responding" dialog to the user. Although the
-                                        // message is in the queue, there is no guarantee when/if
-                                        // the runnable will execute. In the case that the runnable
-                                        // never executes, the user will need to terminate the
-                                        // process manually.
-                                        break;
-                                    }
-                                }
-                            }
-
-                            if (!postedDialog) {
-                                // There's no active webview we can use to show the dialog, so
-                                // wait again. If we never get a usable view, the user will
-                                // never get the chance to terminate the process, and will
-                                // need to do it manually.
-                                sendMessageDelayed(obtainMessage(TIMED_OUT),
-                                        SUBSEQUENT_TIMEOUT_PERIOD);
-                            }
-                        }
-                        break;
-                    }
-                }
-            };
-        }
-    }
-
-    @Override
-    public void run() {
-        Looper.prepare();
-
-        createHandler();
-
-        // Send the initial control to WebViewCore and start the timeout timer as long as we aren't
-        // paused.
-        synchronized (WebCoreThreadWatchdog.class) {
-            if (!mPaused) {
-                mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
-                        mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
-            }
-        }
-
-        Looper.loop();
-    }
-
-    private class PageNotRespondingRunnable implements Runnable {
-        Context mContext;
-        private Handler mWatchdogHandler;
-
-        public PageNotRespondingRunnable(Context context, Handler watchdogHandler) {
-            mContext = context;
-            mWatchdogHandler = watchdogHandler;
-        }
-
-        @Override
-        public void run() {
-            // This must run on the UI thread as it is displaying an AlertDialog.
-            assert Looper.getMainLooper().getThread() == Thread.currentThread();
-            new AlertDialog.Builder(mContext)
-                    .setMessage(com.android.internal.R.string.webpage_unresponsive)
-                    .setPositiveButton(com.android.internal.R.string.force_close,
-                            new DialogInterface.OnClickListener() {
-                                @Override
-                                public void onClick(DialogInterface dialog, int which) {
-                                    // User chose to force close.
-                                    Process.killProcess(Process.myPid());
-                                }
-                            })
-                    .setNegativeButton(com.android.internal.R.string.wait,
-                            new DialogInterface.OnClickListener() {
-                                @Override
-                                public void onClick(DialogInterface dialog, int which) {
-                                    // The user chose to wait. The last HEARTBEAT message
-                                    // will still be in the WebCore thread's queue, so all
-                                    // we need to do is post another TIMED_OUT so that the
-                                    // user will get prompted again if the WebCore thread
-                                    // doesn't sort itself out.
-                                    mWatchdogHandler.sendMessageDelayed(
-                                            mWatchdogHandler.obtainMessage(TIMED_OUT),
-                                            SUBSEQUENT_TIMEOUT_PERIOD);
-                                }
-                            })
-                    .setOnCancelListener(
-                            new DialogInterface.OnCancelListener() {
-                                @Override
-                                public void onCancel(DialogInterface dialog) {
-                                    mWatchdogHandler.sendMessageDelayed(
-                                            mWatchdogHandler.obtainMessage(TIMED_OUT),
-                                            SUBSEQUENT_TIMEOUT_PERIOD);
-                                }
-                            })
-                    .setIconAttribute(android.R.attr.alertDialogIcon)
-                    .show();
-        }
-    }
-}
diff --git a/core/java/android/webkit/WebHistoryItemClassic.java b/core/java/android/webkit/WebHistoryItemClassic.java
deleted file mode 100644
index 1620fbf..0000000
--- a/core/java/android/webkit/WebHistoryItemClassic.java
+++ /dev/null
@@ -1,221 +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;
-
-import android.graphics.Bitmap;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/* package */ class WebHistoryItemClassic extends WebHistoryItem implements Cloneable {
-    // Global identifier count.
-    private static int sNextId = 0;
-    // Unique identifier.
-    private final int mId;
-    // A point to a native WebHistoryItem instance which contains the actual data
-    private int mNativeBridge;
-    // The favicon for this item.
-    private Bitmap mFavicon;
-    // The pre-flattened data used for saving the state.
-    private byte[] mFlattenedData;
-    // The apple-touch-icon url for use when adding the site to the home screen,
-    // as obtained from a <link> element in the page.
-    private String mTouchIconUrlFromLink;
-    // If no <link> is specified, this holds the default location of the
-    // apple-touch-icon.
-    private String mTouchIconUrlServerDefault;
-    // Custom client data that is not flattened or read by native code.
-    private Object mCustomData;
-
-    /**
-     * Basic constructor that assigns a unique id to the item. Called by JNI
-     * only.
-     */
-    private WebHistoryItemClassic(int nativeBridge) {
-        synchronized (WebHistoryItemClassic.class) {
-            mId = sNextId++;
-        }
-        mNativeBridge = nativeBridge;
-        nativeRef(mNativeBridge);
-    }
-
-    protected void finalize() throws Throwable {
-        if (mNativeBridge != 0) {
-            nativeUnref(mNativeBridge);
-            mNativeBridge = 0;
-        }
-    }
-
-    /**
-     * Construct a new WebHistoryItem with initial flattened data.
-     * @param data The pre-flattened data coming from restoreState.
-     */
-    /*package*/ WebHistoryItemClassic(byte[] data) {
-        mFlattenedData = data;
-        synchronized (WebHistoryItemClassic.class) {
-            mId = sNextId++;
-        }
-    }
-
-    /**
-     * Construct a clone of a WebHistoryItem from the given item.
-     * @param item The history item to clone.
-     */
-    private WebHistoryItemClassic(WebHistoryItemClassic item) {
-        mFlattenedData = item.mFlattenedData;
-        mId = item.mId;
-        mFavicon = item.mFavicon;
-        mNativeBridge = item.mNativeBridge;
-        if (mNativeBridge != 0) {
-            nativeRef(mNativeBridge);
-        }
-    }
-
-    @Deprecated
-    public int getId() {
-        return mId;
-    }
-
-    public String getUrl() {
-        if (mNativeBridge == 0) return null;
-        return nativeGetUrl(mNativeBridge);
-    }
-
-    public String getOriginalUrl() {
-        if (mNativeBridge == 0) return null;
-        return nativeGetOriginalUrl(mNativeBridge);
-    }
-
-    public String getTitle() {
-        if (mNativeBridge == 0) return null;
-        return nativeGetTitle(mNativeBridge);
-    }
-
-    public Bitmap getFavicon() {
-        if (mFavicon == null && mNativeBridge != 0) {
-            mFavicon = nativeGetFavicon(mNativeBridge);
-        }
-        return mFavicon;
-    }
-
-    /**
-     * Return the touch icon url.
-     * If no touch icon <link> tag was specified, returns
-     * <host>/apple-touch-icon.png. The DownloadTouchIcon class that
-     * attempts to retrieve the touch icon will handle the case where
-     * that file does not exist. An icon set by a <link> tag is always
-     * used in preference to an icon saved on the server.
-     * @hide
-     */
-    public String getTouchIconUrl() {
-        if (mTouchIconUrlFromLink != null) {
-            return mTouchIconUrlFromLink;
-        } else if (mTouchIconUrlServerDefault != null) {
-            return mTouchIconUrlServerDefault;
-        }
-
-        try {
-            URL url = new URL(getOriginalUrl());
-            mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(),
-                    "/apple-touch-icon.png").toString();
-        } catch (MalformedURLException e) {
-            return null;
-        }
-        return mTouchIconUrlServerDefault;
-    }
-
-    /**
-     * Return the custom data provided by the client.
-     * @hide
-     */
-    public Object getCustomData() {
-        return mCustomData;
-    }
-
-    /**
-     * Set the custom data field.
-     * @param data An Object containing any data the client wishes to associate
-     *             with the item.
-     * @hide
-     */
-    public void setCustomData(Object data) {
-        // NOTE: WebHistoryItems are used in multiple threads. However, the
-        // public facing apis are all getters with the exception of this one
-        // api. Since this api is exclusive to clients, we don't make any
-        // promises about thread safety.
-        mCustomData = data;
-    }
-
-    /**
-     * Set the favicon.
-     * @param icon A Bitmap containing the favicon for this history item.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
-     */
-    /*package*/ void setFavicon(Bitmap icon) {
-        mFavicon = icon;
-    }
-
-    /**
-     * Set the touch icon url. Will not overwrite an icon that has been
-     * set already from a <link> tag, unless the new icon is precomposed.
-     * @hide
-     */
-    /*package*/ void setTouchIconUrl(String url, boolean precomposed) {
-        if (precomposed || mTouchIconUrlFromLink == null) {
-            mTouchIconUrlFromLink = url;
-        }
-    }
-
-    /**
-     * Get the pre-flattened data.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
-     */
-    /*package*/ byte[] getFlattenedData() {
-        if (mNativeBridge != 0) {
-            return nativeGetFlattenedData(mNativeBridge);
-        }
-        return mFlattenedData;
-    }
-
-    /**
-     * Inflate this item.
-     * Note: The VM ensures 32-bit atomic read/write operations so we don't have
-     * to synchronize this method.
-     */
-    /*package*/ void inflate(int nativeFrame) {
-        mNativeBridge = inflate(nativeFrame, mFlattenedData);
-        mFlattenedData = null;
-    }
-
-    public synchronized WebHistoryItemClassic clone() {
-        return new WebHistoryItemClassic(this);
-    }
-
-    /* Natively inflate this item, this method is called in the WebCore thread.
-     */
-    private native int inflate(int nativeFrame, byte[] data);
-    private native void nativeRef(int nptr);
-    private native void nativeUnref(int nptr);
-    private native String nativeGetTitle(int nptr);
-    private native String nativeGetUrl(int nptr);
-    private native String nativeGetOriginalUrl(int nptr);
-    private native byte[] nativeGetFlattenedData(int nptr);
-    private native Bitmap nativeGetFavicon(int nptr);
-
-}
diff --git a/core/java/android/webkit/WebIconDatabaseClassic.java b/core/java/android/webkit/WebIconDatabaseClassic.java
deleted file mode 100644
index d6c4c33..0000000
--- a/core/java/android/webkit/WebIconDatabaseClassic.java
+++ /dev/null
@@ -1,289 +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;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Browser;
-import android.util.Log;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Vector;
-
-class WebIconDatabaseClassic extends WebIconDatabase {
-    private static final String LOGTAG = "WebIconDatabase";
-    // Global instance of a WebIconDatabase
-    private static WebIconDatabaseClassic sIconDatabase;
-    // EventHandler for handling messages before and after the WebCore thread is
-    // ready.
-    private final EventHandler mEventHandler = new EventHandler();
-
-    // Class to handle messages before WebCore is ready
-    private static class EventHandler extends Handler {
-        // Message ids
-        static final int OPEN         = 0;
-        static final int CLOSE        = 1;
-        static final int REMOVE_ALL   = 2;
-        static final int REQUEST_ICON = 3;
-        static final int RETAIN_ICON  = 4;
-        static final int RELEASE_ICON = 5;
-        static final int BULK_REQUEST_ICON = 6;
-        // Message for dispatching icon request results
-        private static final int ICON_RESULT = 10;
-        // Actual handler that runs in WebCore thread
-        private Handler mHandler;
-        // Vector of messages before the WebCore thread is ready
-        private Vector<Message> mMessages = new Vector<Message>();
-        // Class to handle a result dispatch
-        private class IconResult {
-            private final String mUrl;
-            private final Bitmap mIcon;
-            private final IconListener mListener;
-            IconResult(String url, Bitmap icon, IconListener l) {
-                mUrl = url;
-                mIcon = icon;
-                mListener = l;
-            }
-            void dispatch() {
-                mListener.onReceivedIcon(mUrl, mIcon);
-            }
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            // Note: This is the message handler for the UI thread.
-            switch (msg.what) {
-                case ICON_RESULT:
-                    ((IconResult) msg.obj).dispatch();
-                    break;
-            }
-        }
-
-        // Called by WebCore thread to create the actual handler
-        private synchronized void createHandler() {
-            if (mHandler == null) {
-                mHandler = new Handler() {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        // Note: This is the message handler for the WebCore
-                        // thread.
-                        switch (msg.what) {
-                            case OPEN:
-                                nativeOpen((String) msg.obj);
-                                break;
-
-                            case CLOSE:
-                                nativeClose();
-                                break;
-
-                            case REMOVE_ALL:
-                                nativeRemoveAllIcons();
-                                break;
-
-                            case REQUEST_ICON:
-                                IconListener l = (IconListener) msg.obj;
-                                String url = msg.getData().getString("url");
-                                requestIconAndSendResult(url, l);
-                                break;
-
-                            case BULK_REQUEST_ICON:
-                                bulkRequestIcons(msg);
-                                break;
-
-                            case RETAIN_ICON:
-                                nativeRetainIconForPageUrl((String) msg.obj);
-                                break;
-
-                            case RELEASE_ICON:
-                                nativeReleaseIconForPageUrl((String) msg.obj);
-                                break;
-                        }
-                    }
-                };
-                // Transfer all pending messages
-                for (int size = mMessages.size(); size > 0; size--) {
-                    mHandler.sendMessage(mMessages.remove(0));
-                }
-                mMessages = null;
-            }
-        }
-
-        private synchronized boolean hasHandler() {
-            return mHandler != null;
-        }
-
-        private synchronized void postMessage(Message msg) {
-            if (mMessages != null) {
-                mMessages.add(msg);
-            } else {
-                mHandler.sendMessage(msg);
-            }
-        }
-
-        private void bulkRequestIcons(Message msg) {
-            HashMap map = (HashMap) msg.obj;
-            IconListener listener = (IconListener) map.get("listener");
-            ContentResolver cr = (ContentResolver) map.get("contentResolver");
-            String where = (String) map.get("where");
-
-            Cursor c = null;
-            try {
-                c = cr.query(
-                        Browser.BOOKMARKS_URI,
-                        new String[] { Browser.BookmarkColumns.URL },
-                        where, null, null);
-                if (c.moveToFirst()) {
-                    do {
-                        String url = c.getString(0);
-                        requestIconAndSendResult(url, listener);
-                    } while (c.moveToNext());
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "BulkRequestIcons", e);
-            } finally {
-                if (c != null) c.close();
-            }
-        }
-
-        private void requestIconAndSendResult(String url, IconListener listener) {
-            Bitmap icon = nativeIconForPageUrl(url);
-            if (icon != null) {
-                sendMessage(obtainMessage(ICON_RESULT,
-                            new IconResult(url, icon, listener)));
-            }
-        }
-    }
-
-    @Override
-    public void open(String path) {
-        if (path != null) {
-            // Make the directories and parents if they don't exist
-            File db = new File(path);
-            if (!db.exists()) {
-                db.mkdirs();
-            }
-            mEventHandler.postMessage(
-                    Message.obtain(null, EventHandler.OPEN, db.getAbsolutePath()));
-        }
-    }
-
-    @Override
-    public void close() {
-        mEventHandler.postMessage(
-                Message.obtain(null, EventHandler.CLOSE));
-    }
-
-    @Override
-    public void removeAllIcons() {
-        mEventHandler.postMessage(
-                Message.obtain(null, EventHandler.REMOVE_ALL));
-    }
-
-    /**
-     * Request the Bitmap representing the icon for the given page
-     * url. If the icon exists, the listener will be called with the result.
-     * @param url The page's url.
-     * @param listener An implementation on IconListener to receive the result.
-     */
-    public void requestIconForPageUrl(String url, IconListener listener) {
-        if (listener == null || url == null) {
-            return;
-        }
-        Message msg = Message.obtain(null, EventHandler.REQUEST_ICON, listener);
-        msg.getData().putString("url", url);
-        mEventHandler.postMessage(msg);
-    }
-
-    /** {@hide}
-     */
-    public void bulkRequestIconForPageUrl(ContentResolver cr, String where,
-            IconListener listener) {
-        if (listener == null) {
-            return;
-        }
-
-        // Special case situation: we don't want to add this message to the
-        // queue if there is no handler because we may never have a real
-        // handler to service the messages and the cursor will never get
-        // closed.
-        if (mEventHandler.hasHandler()) {
-            // Don't use Bundle as it is parcelable.
-            HashMap<String, Object> map = new HashMap<String, Object>();
-            map.put("contentResolver", cr);
-            map.put("where", where);
-            map.put("listener", listener);
-            Message msg =
-                    Message.obtain(null, EventHandler.BULK_REQUEST_ICON, map);
-            mEventHandler.postMessage(msg);
-        }
-    }
-
-    @Override
-    public void retainIconForPageUrl(String url) {
-        if (url != null) {
-            mEventHandler.postMessage(
-                    Message.obtain(null, EventHandler.RETAIN_ICON, url));
-        }
-    }
-
-    @Override
-    public void releaseIconForPageUrl(String url) {
-        if (url != null) {
-            mEventHandler.postMessage(
-                    Message.obtain(null, EventHandler.RELEASE_ICON, url));
-        }
-    }
-
-    /**
-     * Get the global instance of WebIconDatabase.
-     * @return A single instance of WebIconDatabase. It will be the same
-     *         instance for the current process each time this method is
-     *         called.
-     */
-    public static WebIconDatabaseClassic getInstance() {
-        // XXX: Must be created in the UI thread.
-        if (sIconDatabase == null) {
-            sIconDatabase = new WebIconDatabaseClassic();
-        }
-        return sIconDatabase;
-    }
-
-    /**
-     * Create the internal handler and transfer all pending messages.
-     * XXX: Called by WebCore thread only!
-     */
-    /*package*/ void createHandler() {
-        mEventHandler.createHandler();
-    }
-
-    /**
-     * Private constructor to avoid anyone else creating an instance.
-     */
-    private WebIconDatabaseClassic() {}
-
-    // Native functions
-    private static native void nativeOpen(String path);
-    private static native void nativeClose();
-    private static native void nativeRemoveAllIcons();
-    private static native Bitmap nativeIconForPageUrl(String url);
-    private static native void nativeRetainIconForPageUrl(String url);
-    private static native void nativeReleaseIconForPageUrl(String url);
-}
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
deleted file mode 100644
index c10a429..0000000
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ /dev/null
@@ -1,1744 +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;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.util.EventLog;
-
-import java.util.Locale;
-
-/**
- * WebSettings implementation for the WebViewClassic implementation of WebView.
- * @hide
- */
-public class WebSettingsClassic extends WebSettings {
-    // TODO: Keep this up to date
-    private static final String PREVIOUS_VERSION = "4.1.1";
-
-    // WebView associated with this WebSettings.
-    private WebViewClassic mWebView;
-    // BrowserFrame used to access the native frame pointer.
-    private BrowserFrame mBrowserFrame;
-    // Flag to prevent multiple SYNC messages at one time.
-    private boolean mSyncPending = false;
-    // Custom handler that queues messages until the WebCore thread is active.
-    private final EventHandler mEventHandler;
-
-    // Private settings so we don't have to go into native code to
-    // retrieve the values. After setXXX, postSync() needs to be called.
-    //
-    // The default values need to match those in WebSettings.cpp
-    // If the defaults change, please also update the JavaDocs so developers
-    // know what they are.
-    private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
-    private Context         mContext;
-    private int             mTextSize = 100;
-    private String          mStandardFontFamily = "sans-serif";
-    private String          mFixedFontFamily = "monospace";
-    private String          mSansSerifFontFamily = "sans-serif";
-    private String          mSerifFontFamily = "serif";
-    private String          mCursiveFontFamily = "cursive";
-    private String          mFantasyFontFamily = "fantasy";
-    private String          mDefaultTextEncoding;
-    private String          mUserAgent;
-    private boolean         mUseDefaultUserAgent;
-    private String          mAcceptLanguage;
-    private int             mMinimumFontSize = 8;
-    private int             mMinimumLogicalFontSize = 8;
-    private int             mDefaultFontSize = 16;
-    private int             mDefaultFixedFontSize = 13;
-    private int             mPageCacheCapacity = 0;
-    private boolean         mLoadsImagesAutomatically = true;
-    private boolean         mBlockNetworkImage = false;
-    private boolean         mBlockNetworkLoads;
-    private boolean         mJavaScriptEnabled = false;
-    private boolean         mAllowUniversalAccessFromFileURLs = false;
-    private boolean         mAllowFileAccessFromFileURLs = false;
-    private boolean         mHardwareAccelSkia = false;
-    private boolean         mShowVisualIndicator = false;
-    private PluginState     mPluginState = PluginState.OFF;
-    private boolean         mJavaScriptCanOpenWindowsAutomatically = false;
-    private boolean         mUseDoubleTree = false;
-    private boolean         mUseWideViewport = false;
-    private boolean         mSupportMultipleWindows = false;
-    private boolean         mShrinksStandaloneImagesToFit = false;
-    private long            mMaximumDecodedImageSize = 0; // 0 means default
-    private boolean         mPrivateBrowsingEnabled = false;
-    private boolean         mSyntheticLinksEnabled = true;
-    // HTML5 API flags
-    private boolean         mAppCacheEnabled = false;
-    private boolean         mDatabaseEnabled = false;
-    private boolean         mDomStorageEnabled = false;
-    private boolean         mWorkersEnabled = false;  // only affects V8.
-    private boolean         mGeolocationEnabled = true;
-    private boolean         mXSSAuditorEnabled = false;
-    private boolean         mLinkPrefetchEnabled = false;
-    // HTML5 configuration parameters
-    private long            mAppCacheMaxSize = Long.MAX_VALUE;
-    private String          mAppCachePath = null;
-    private String          mDatabasePath = "";
-    // The WebCore DatabaseTracker only allows the database path to be set
-    // once. Keep track of when the path has been set.
-    private boolean         mDatabasePathHasBeenSet = false;
-    private String          mGeolocationDatabasePath = "";
-    // Don't need to synchronize the get/set methods as they
-    // are basic types, also none of these values are used in
-    // native WebCore code.
-    private ZoomDensity     mDefaultZoom = ZoomDensity.MEDIUM;
-    private RenderPriority  mRenderPriority = RenderPriority.NORMAL;
-    private int             mOverrideCacheMode = LOAD_DEFAULT;
-    private int             mDoubleTapZoom = 100;
-    private boolean         mSaveFormData = true;
-    private boolean         mAutoFillEnabled = false;
-    private boolean         mSavePassword = true;
-    private boolean         mLightTouchEnabled = false;
-    private boolean         mNeedInitialFocus = true;
-    private boolean         mNavDump = false;
-    private boolean         mSupportZoom = true;
-    private boolean         mMediaPlaybackRequiresUserGesture = true;
-    private boolean         mBuiltInZoomControls = false;
-    private boolean         mDisplayZoomControls = true;
-    private boolean         mAllowFileAccess = true;
-    private boolean         mAllowContentAccess = true;
-    private boolean         mLoadWithOverviewMode = false;
-    private boolean         mEnableSmoothTransition = false;
-    private boolean         mForceUserScalable = false;
-    private boolean         mPasswordEchoEnabled = true;
-
-    // AutoFill Profile data
-    public static class AutoFillProfile {
-        private int mUniqueId;
-        private String mFullName;
-        private String mEmailAddress;
-        private String mCompanyName;
-        private String mAddressLine1;
-        private String mAddressLine2;
-        private String mCity;
-        private String mState;
-        private String mZipCode;
-        private String mCountry;
-        private String mPhoneNumber;
-
-        public AutoFillProfile(int uniqueId, String fullName, String email,
-                String companyName, String addressLine1, String addressLine2,
-                String city, String state, String zipCode, String country,
-                String phoneNumber) {
-            mUniqueId = uniqueId;
-            mFullName = fullName;
-            mEmailAddress = email;
-            mCompanyName = companyName;
-            mAddressLine1 = addressLine1;
-            mAddressLine2 = addressLine2;
-            mCity = city;
-            mState = state;
-            mZipCode = zipCode;
-            mCountry = country;
-            mPhoneNumber = phoneNumber;
-        }
-
-        public int getUniqueId() { return mUniqueId; }
-        public String getFullName() { return mFullName; }
-        public String getEmailAddress() { return mEmailAddress; }
-        public String getCompanyName() { return mCompanyName; }
-        public String getAddressLine1() { return mAddressLine1; }
-        public String getAddressLine2() { return mAddressLine2; }
-        public String getCity() { return mCity; }
-        public String getState() { return mState; }
-        public String getZipCode() { return mZipCode; }
-        public String getCountry() { return mCountry; }
-        public String getPhoneNumber() { return mPhoneNumber; }
-    }
-
-
-    private AutoFillProfile mAutoFillProfile;
-
-    private boolean         mUseWebViewBackgroundForOverscroll = true;
-
-    // private WebSettings, not accessible by the host activity
-    static private int      mDoubleTapToastCount = 3;
-
-    private static final String PREF_FILE = "WebViewSettings";
-    private static final String DOUBLE_TAP_TOAST_COUNT = "double_tap_toast_count";
-
-    // Class to handle messages before WebCore is ready.
-    private class EventHandler {
-        // Message id for syncing
-        static final int SYNC = 0;
-        // Message id for setting priority
-        static final int PRIORITY = 1;
-        // Message id for writing double-tap toast count
-        static final int SET_DOUBLE_TAP_TOAST_COUNT = 2;
-        // Actual WebCore thread handler
-        private Handler mHandler;
-
-        private synchronized void createHandler() {
-            // as mRenderPriority can be set before thread is running, sync up
-            setRenderPriority();
-
-            // create a new handler
-            mHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case SYNC:
-                            synchronized (WebSettingsClassic.this) {
-                                if (mBrowserFrame.mNativeFrame != 0) {
-                                    nativeSync(mBrowserFrame.mNativeFrame);
-                                }
-                                mSyncPending = false;
-                            }
-                            break;
-
-                        case PRIORITY: {
-                            setRenderPriority();
-                            break;
-                        }
-
-                        case SET_DOUBLE_TAP_TOAST_COUNT: {
-                            SharedPreferences.Editor editor = mContext
-                                    .getSharedPreferences(PREF_FILE,
-                                            Context.MODE_PRIVATE).edit();
-                            editor.putInt(DOUBLE_TAP_TOAST_COUNT,
-                                    mDoubleTapToastCount);
-                            editor.commit();
-                            break;
-                        }
-                    }
-                }
-            };
-        }
-
-        private void setRenderPriority() {
-            synchronized (WebSettingsClassic.this) {
-                if (mRenderPriority == RenderPriority.NORMAL) {
-                    android.os.Process.setThreadPriority(
-                            android.os.Process.THREAD_PRIORITY_DEFAULT);
-                } else if (mRenderPriority == RenderPriority.HIGH) {
-                    android.os.Process.setThreadPriority(
-                            android.os.Process.THREAD_PRIORITY_FOREGROUND +
-                            android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
-                } else if (mRenderPriority == RenderPriority.LOW) {
-                    android.os.Process.setThreadPriority(
-                            android.os.Process.THREAD_PRIORITY_BACKGROUND);
-                }
-            }
-        }
-
-        /**
-         * Send a message to the private queue or handler.
-         */
-        private synchronized boolean sendMessage(Message msg) {
-            if (mHandler != null) {
-                mHandler.sendMessage(msg);
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-
-    // User agent strings.
-    private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " +
-        "Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) " +
-        "Chrome/11.0.696.34 Safari/534.24";
-    private static final String IPHONE_USERAGENT =
-            "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
-            + " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0"
-            + " Mobile/7A341 Safari/528.16";
-    private static Locale sLocale;
-    private static Object sLockForLocaleSettings;
-
-    /**
-     * Package constructor to prevent clients from creating a new settings
-     * instance.
-     */
-    WebSettingsClassic(Context context, WebViewClassic webview) {
-        mEventHandler = new EventHandler();
-        mContext = context;
-        mWebView = webview;
-        mDefaultTextEncoding = context.getString(com.android.internal.
-                                                 R.string.default_text_encoding);
-
-        if (sLockForLocaleSettings == null) {
-            sLockForLocaleSettings = new Object();
-            sLocale = Locale.getDefault();
-        }
-        mAcceptLanguage = getCurrentAcceptLanguage();
-        mUserAgent = getCurrentUserAgent();
-        mUseDefaultUserAgent = true;
-
-        mBlockNetworkLoads = mContext.checkPermission(
-                "android.permission.INTERNET", android.os.Process.myPid(),
-                android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED;
-
-        // SDK specific settings. See issue 6212665
-        if (mContext.getApplicationInfo().targetSdkVersion <
-                Build.VERSION_CODES.JELLY_BEAN) {
-            mAllowUniversalAccessFromFileURLs = true;
-            mAllowFileAccessFromFileURLs = true;
-        }
-        try {
-            mPasswordEchoEnabled =
-                    Settings.System.getInt(context.getContentResolver(),
-                        Settings.System.TEXT_SHOW_PASSWORD) != 0;
-        } catch (SettingNotFoundException e) {
-            mPasswordEchoEnabled = true;
-        }
-    }
-
-    private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
-
-    /**
-     * Looks at sLocale and returns current AcceptLanguage String.
-     * @return Current AcceptLanguage String.
-     */
-    private String getCurrentAcceptLanguage() {
-        Locale locale;
-        synchronized(sLockForLocaleSettings) {
-            locale = sLocale;
-        }
-        StringBuilder buffer = new StringBuilder();
-        addLocaleToHttpAcceptLanguage(buffer, locale);
-
-        if (!Locale.US.equals(locale)) {
-            if (buffer.length() > 0) {
-                buffer.append(", ");
-            }
-            buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
-        }
-
-        return buffer.toString();
-    }
-
-    /**
-     * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
-     * to new standard.
-     */
-    private static String convertObsoleteLanguageCodeToNew(String langCode) {
-        if (langCode == null) {
-            return null;
-        }
-        if ("iw".equals(langCode)) {
-            // Hebrew
-            return "he";
-        } else if ("in".equals(langCode)) {
-            // Indonesian
-            return "id";
-        } else if ("ji".equals(langCode)) {
-            // Yiddish
-            return "yi";
-        }
-        return langCode;
-    }
-
-    private static void addLocaleToHttpAcceptLanguage(StringBuilder builder,
-                                                      Locale locale) {
-        String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
-        if (language != null) {
-            builder.append(language);
-            String country = locale.getCountry();
-            if (country != null) {
-                builder.append("-");
-                builder.append(country);
-            }
-        }
-    }
-
-    /**
-     * Looks at sLocale and mContext and returns current UserAgent String.
-     * @return Current UserAgent String.
-     */
-    private synchronized String getCurrentUserAgent() {
-        Locale locale;
-        synchronized(sLockForLocaleSettings) {
-            locale = sLocale;
-        }
-        return getDefaultUserAgentForLocale(mContext, locale);
-    }
-
-    /**
-     * Returns the default User-Agent used by a WebView.
-     * An instance of WebView could use a different User-Agent if a call
-     * is made to {@link WebSettings#setUserAgent(int)} or
-     * {@link WebSettings#setUserAgentString(String)}.
-     *
-     * @param context a Context object used to access application assets
-     * @param locale The Locale to use in the User-Agent string.
-     * @see WebViewFactoryProvider#getDefaultUserAgent(Context)
-     * @see WebView#getDefaultUserAgent(Context)
-     */
-    public static String getDefaultUserAgentForLocale(Context context, Locale locale) {
-        StringBuffer buffer = new StringBuffer();
-        // Add version
-        final String version = Build.VERSION.RELEASE;
-        if (version.length() > 0) {
-            if (Character.isDigit(version.charAt(0))) {
-                // Release is a version, eg "3.1"
-                buffer.append(version);
-            } else {
-                // Release is a codename, eg "Honeycomb"
-                // In this case, use the previous release's version
-                buffer.append(PREVIOUS_VERSION);
-            }
-        } else {
-            // default to "1.0"
-            buffer.append("1.0");
-        }
-        buffer.append("; ");
-        final String language = locale.getLanguage();
-        if (language != null) {
-            buffer.append(convertObsoleteLanguageCodeToNew(language));
-            final String country = locale.getCountry();
-            if (country != null) {
-                buffer.append("-");
-                buffer.append(country.toLowerCase());
-            }
-        } else {
-            // default to "en"
-            buffer.append("en");
-        }
-        buffer.append(";");
-        // add the model for the release build
-        if ("REL".equals(Build.VERSION.CODENAME)) {
-            final String model = Build.MODEL;
-            if (model.length() > 0) {
-                buffer.append(" ");
-                buffer.append(model);
-            }
-        }
-        final String id = Build.ID;
-        if (id.length() > 0) {
-            buffer.append(" Build/");
-            buffer.append(id);
-        }
-        String mobile = context.getResources().getText(
-            com.android.internal.R.string.web_user_agent_target_content).toString();
-        final String base = context.getResources().getText(
-                com.android.internal.R.string.web_user_agent).toString();
-        return String.format(base, buffer, mobile);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setNavDump(boolean)
-     */
-    @Override
-    @Deprecated
-    public void setNavDump(boolean enabled) {
-        mNavDump = enabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getNavDump()
-     */
-    @Override
-    @Deprecated
-    public boolean getNavDump() {
-        return mNavDump;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSupportZoom(boolean)
-     */
-    @Override
-    public void setSupportZoom(boolean support) {
-        mSupportZoom = support;
-        mWebView.updateMultiTouchSupport(mContext);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#supportZoom()
-     */
-    @Override
-    public boolean supportZoom() {
-        return mSupportZoom;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture(boolean)
-     */
-    @Override
-    public void setMediaPlaybackRequiresUserGesture(boolean support) {
-        if (mMediaPlaybackRequiresUserGesture != support) {
-            mMediaPlaybackRequiresUserGesture = support;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture()
-     */
-    @Override
-    public boolean getMediaPlaybackRequiresUserGesture() {
-        return mMediaPlaybackRequiresUserGesture;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setBuiltInZoomControls(boolean)
-     */
-    @Override
-    public void setBuiltInZoomControls(boolean enabled) {
-        mBuiltInZoomControls = enabled;
-        mWebView.updateMultiTouchSupport(mContext);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getBuiltInZoomControls()
-     */
-    @Override
-    public boolean getBuiltInZoomControls() {
-        return mBuiltInZoomControls;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDisplayZoomControls(boolean)
-     */
-    @Override
-    public void setDisplayZoomControls(boolean enabled) {
-        mDisplayZoomControls = enabled;
-        mWebView.updateMultiTouchSupport(mContext);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDisplayZoomControls()
-     */
-    @Override
-    public boolean getDisplayZoomControls() {
-        return mDisplayZoomControls;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAllowFileAccess(boolean)
-     */
-    @Override
-    public void setAllowFileAccess(boolean allow) {
-        mAllowFileAccess = allow;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getAllowFileAccess()
-     */
-    @Override
-    public boolean getAllowFileAccess() {
-        return mAllowFileAccess;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAllowContentAccess(boolean)
-     */
-    @Override
-    public void setAllowContentAccess(boolean allow) {
-        mAllowContentAccess = allow;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getAllowContentAccess()
-     */
-    @Override
-    public boolean getAllowContentAccess() {
-        return mAllowContentAccess;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setLoadWithOverviewMode(boolean)
-     */
-    @Override
-    public void setLoadWithOverviewMode(boolean overview) {
-        mLoadWithOverviewMode = overview;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getLoadWithOverviewMode()
-     */
-    @Override
-    public boolean getLoadWithOverviewMode() {
-        return mLoadWithOverviewMode;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setEnableSmoothTransition(boolean)
-     */
-    @Override
-    public void setEnableSmoothTransition(boolean enable) {
-        mEnableSmoothTransition = enable;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#enableSmoothTransition()
-     */
-    @Override
-    public boolean enableSmoothTransition() {
-        return mEnableSmoothTransition;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setUseWebViewBackgroundForOverscrollBackground(boolean)
-     */
-    @Override
-    @Deprecated
-    public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
-        mUseWebViewBackgroundForOverscroll = view;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getUseWebViewBackgroundForOverscrollBackground()
-     */
-    @Override
-    @Deprecated
-    public boolean getUseWebViewBackgroundForOverscrollBackground() {
-        return mUseWebViewBackgroundForOverscroll;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSaveFormData(boolean)
-     */
-    @Override
-    public void setSaveFormData(boolean save) {
-        mSaveFormData = save;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getSaveFormData()
-     */
-    @Override
-    public boolean getSaveFormData() {
-        return mSaveFormData && !mPrivateBrowsingEnabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSavePassword(boolean)
-     */
-    @Override
-    public void setSavePassword(boolean save) {
-        mSavePassword = save;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getSavePassword()
-     */
-    @Override
-    public boolean getSavePassword() {
-        return mSavePassword;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setTextZoom(int)
-     */
-    @Override
-    public synchronized void setTextZoom(int textZoom) {
-        if (mTextSize != textZoom) {
-            mTextSize = textZoom;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getTextZoom()
-     */
-    @Override
-    public synchronized int getTextZoom() {
-        return mTextSize;
-    }
-
-    /**
-     * 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.
-     */
-    public void setDoubleTapZoom(int doubleTapZoom) {
-        if (mDoubleTapZoom != doubleTapZoom) {
-            mDoubleTapZoom = doubleTapZoom;
-            mWebView.updateDoubleTapZoom(doubleTapZoom);
-        }
-    }
-
-    /**
-     * Get the double-tap zoom of the page in percent.
-     * @return A percent value describing the double-tap zoom.
-     */
-    public int getDoubleTapZoom() {
-        return mDoubleTapZoom;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDefaultZoom(android.webkit.WebSettingsClassic.ZoomDensity)
-     */
-    @Override
-    public void setDefaultZoom(ZoomDensity zoom) {
-        if (mDefaultZoom != zoom) {
-            mDefaultZoom = zoom;
-            mWebView.adjustDefaultZoomDensity(zoom.value);
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDefaultZoom()
-     */
-    @Override
-    public ZoomDensity getDefaultZoom() {
-        return mDefaultZoom;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setLightTouchEnabled(boolean)
-     */
-    @Override
-    public void setLightTouchEnabled(boolean enabled) {
-        mLightTouchEnabled = enabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getLightTouchEnabled()
-     */
-    @Override
-    public boolean getLightTouchEnabled() {
-        return mLightTouchEnabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setUseDoubleTree(boolean)
-     */
-    @Override
-    @Deprecated
-    public synchronized void setUseDoubleTree(boolean use) {
-        return;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getUseDoubleTree()
-     */
-    @Override
-    @Deprecated
-    public synchronized boolean getUseDoubleTree() {
-        return false;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setUserAgent(int)
-     */
-    @Override
-    @Deprecated
-    public synchronized void setUserAgent(int ua) {
-        String uaString = null;
-        if (ua == 1) {
-            if (DESKTOP_USERAGENT.equals(mUserAgent)) {
-                return; // do nothing
-            } else {
-                uaString = DESKTOP_USERAGENT;
-            }
-        } else if (ua == 2) {
-            if (IPHONE_USERAGENT.equals(mUserAgent)) {
-                return; // do nothing
-            } else {
-                uaString = IPHONE_USERAGENT;
-            }
-        } else if (ua != 0) {
-            return; // do nothing
-        }
-        setUserAgentString(uaString);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getUserAgent()
-     */
-    @Override
-    @Deprecated
-    public synchronized int getUserAgent() {
-        if (DESKTOP_USERAGENT.equals(mUserAgent)) {
-            return 1;
-        } else if (IPHONE_USERAGENT.equals(mUserAgent)) {
-            return 2;
-        } else if (mUseDefaultUserAgent) {
-            return 0;
-        }
-        return -1;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setUseWideViewPort(boolean)
-     */
-    @Override
-    public synchronized void setUseWideViewPort(boolean use) {
-        if (mUseWideViewport != use) {
-            mUseWideViewport = use;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getUseWideViewPort()
-     */
-    @Override
-    public synchronized boolean getUseWideViewPort() {
-        return mUseWideViewport;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSupportMultipleWindows(boolean)
-     */
-    @Override
-    public synchronized void setSupportMultipleWindows(boolean support) {
-        if (mSupportMultipleWindows != support) {
-            mSupportMultipleWindows = support;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#supportMultipleWindows()
-     */
-    @Override
-    public synchronized boolean supportMultipleWindows() {
-        return mSupportMultipleWindows;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setLayoutAlgorithm(android.webkit.WebSettingsClassic.LayoutAlgorithm)
-     */
-    @Override
-    public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
-        if (l == LayoutAlgorithm.TEXT_AUTOSIZING) {
-            throw new IllegalArgumentException(
-                    "WebViewClassic does not support TEXT_AUTOSIZING layout mode");
-        }
-        // XXX: This will only be affective if libwebcore was built with
-        // ANDROID_LAYOUT defined.
-        if (mLayoutAlgorithm != l) {
-            mLayoutAlgorithm = l;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getLayoutAlgorithm()
-     */
-    @Override
-    public synchronized LayoutAlgorithm getLayoutAlgorithm() {
-        return mLayoutAlgorithm;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setStandardFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setStandardFontFamily(String font) {
-        if (font != null && !font.equals(mStandardFontFamily)) {
-            mStandardFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getStandardFontFamily()
-     */
-    @Override
-    public synchronized String getStandardFontFamily() {
-        return mStandardFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setFixedFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setFixedFontFamily(String font) {
-        if (font != null && !font.equals(mFixedFontFamily)) {
-            mFixedFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getFixedFontFamily()
-     */
-    @Override
-    public synchronized String getFixedFontFamily() {
-        return mFixedFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSansSerifFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setSansSerifFontFamily(String font) {
-        if (font != null && !font.equals(mSansSerifFontFamily)) {
-            mSansSerifFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getSansSerifFontFamily()
-     */
-    @Override
-    public synchronized String getSansSerifFontFamily() {
-        return mSansSerifFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setSerifFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setSerifFontFamily(String font) {
-        if (font != null && !font.equals(mSerifFontFamily)) {
-            mSerifFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getSerifFontFamily()
-     */
-    @Override
-    public synchronized String getSerifFontFamily() {
-        return mSerifFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setCursiveFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setCursiveFontFamily(String font) {
-        if (font != null && !font.equals(mCursiveFontFamily)) {
-            mCursiveFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getCursiveFontFamily()
-     */
-    @Override
-    public synchronized String getCursiveFontFamily() {
-        return mCursiveFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setFantasyFontFamily(java.lang.String)
-     */
-    @Override
-    public synchronized void setFantasyFontFamily(String font) {
-        if (font != null && !font.equals(mFantasyFontFamily)) {
-            mFantasyFontFamily = font;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getFantasyFontFamily()
-     */
-    @Override
-    public synchronized String getFantasyFontFamily() {
-        return mFantasyFontFamily;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setMinimumFontSize(int)
-     */
-    @Override
-    public synchronized void setMinimumFontSize(int size) {
-        size = pin(size);
-        if (mMinimumFontSize != size) {
-            mMinimumFontSize = size;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getMinimumFontSize()
-     */
-    @Override
-    public synchronized int getMinimumFontSize() {
-        return mMinimumFontSize;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setMinimumLogicalFontSize(int)
-     */
-    @Override
-    public synchronized void setMinimumLogicalFontSize(int size) {
-        size = pin(size);
-        if (mMinimumLogicalFontSize != size) {
-            mMinimumLogicalFontSize = size;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getMinimumLogicalFontSize()
-     */
-    @Override
-    public synchronized int getMinimumLogicalFontSize() {
-        return mMinimumLogicalFontSize;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDefaultFontSize(int)
-     */
-    @Override
-    public synchronized void setDefaultFontSize(int size) {
-        size = pin(size);
-        if (mDefaultFontSize != size) {
-            mDefaultFontSize = size;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDefaultFontSize()
-     */
-    @Override
-    public synchronized int getDefaultFontSize() {
-        return mDefaultFontSize;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDefaultFixedFontSize(int)
-     */
-    @Override
-    public synchronized void setDefaultFixedFontSize(int size) {
-        size = pin(size);
-        if (mDefaultFixedFontSize != size) {
-            mDefaultFixedFontSize = size;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDefaultFixedFontSize()
-     */
-    @Override
-    public synchronized int getDefaultFixedFontSize() {
-        return mDefaultFixedFontSize;
-    }
-
-    /**
-     * 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).
-     */
-    public synchronized void setPageCacheCapacity(int size) {
-        if (size < 0) size = 0;
-        if (size > 20) size = 20;
-        if (mPageCacheCapacity != size) {
-            mPageCacheCapacity = size;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setLoadsImagesAutomatically(boolean)
-     */
-    @Override
-    public synchronized void setLoadsImagesAutomatically(boolean flag) {
-        if (mLoadsImagesAutomatically != flag) {
-            mLoadsImagesAutomatically = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getLoadsImagesAutomatically()
-     */
-    @Override
-    public synchronized boolean getLoadsImagesAutomatically() {
-        return mLoadsImagesAutomatically;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setBlockNetworkImage(boolean)
-     */
-    @Override
-    public synchronized void setBlockNetworkImage(boolean flag) {
-        if (mBlockNetworkImage != flag) {
-            mBlockNetworkImage = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getBlockNetworkImage()
-     */
-    @Override
-    public synchronized boolean getBlockNetworkImage() {
-        return mBlockNetworkImage;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setBlockNetworkLoads(boolean)
-     */
-    @Override
-    public synchronized void setBlockNetworkLoads(boolean flag) {
-        if (mBlockNetworkLoads != flag) {
-            mBlockNetworkLoads = flag;
-            verifyNetworkAccess();
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getBlockNetworkLoads()
-     */
-    @Override
-    public synchronized boolean getBlockNetworkLoads() {
-        return mBlockNetworkLoads;
-    }
-
-
-    private void verifyNetworkAccess() {
-        if (!mBlockNetworkLoads) {
-            if (mContext.checkPermission("android.permission.INTERNET",
-                    android.os.Process.myPid(), android.os.Process.myUid()) !=
-                        PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException
-                        ("Permission denied - " +
-                                "application missing INTERNET permission");
-            }
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setJavaScriptEnabled(boolean)
-     */
-    @Override
-    public synchronized void setJavaScriptEnabled(boolean flag) {
-        if (mJavaScriptEnabled != flag) {
-            mJavaScriptEnabled = flag;
-            postSync();
-            mWebView.updateJavaScriptEnabled(flag);
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAllowUniversalAccessFromFileURLs
-     */
-    @Override
-    public synchronized void setAllowUniversalAccessFromFileURLs(boolean flag) {
-        if (mAllowUniversalAccessFromFileURLs != flag) {
-            mAllowUniversalAccessFromFileURLs = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAllowFileAccessFromFileURLs
-     */
-    @Override
-    public synchronized void setAllowFileAccessFromFileURLs(boolean flag) {
-        if (mAllowFileAccessFromFileURLs != flag) {
-            mAllowFileAccessFromFileURLs = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * Tell the WebView to use Skia's hardware accelerated rendering path
-     * @param flag True if the WebView should use Skia's hw-accel path
-     */
-    public synchronized void setHardwareAccelSkiaEnabled(boolean flag) {
-        if (mHardwareAccelSkia != flag) {
-            mHardwareAccelSkia = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @return True if the WebView is using hardware accelerated skia
-     */
-    public synchronized boolean getHardwareAccelSkiaEnabled() {
-        return mHardwareAccelSkia;
-    }
-
-    /**
-     * Tell the WebView to show the visual indicator
-     * @param flag True if the WebView should show the visual indicator
-     */
-    public synchronized void setShowVisualIndicator(boolean flag) {
-        if (mShowVisualIndicator != flag) {
-            mShowVisualIndicator = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @return True if the WebView is showing the visual indicator
-     */
-    public synchronized boolean getShowVisualIndicator() {
-        return mShowVisualIndicator;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setPluginsEnabled(boolean)
-     */
-    @Override
-    @Deprecated
-    public synchronized void setPluginsEnabled(boolean flag) {
-        setPluginState(flag ? PluginState.ON : PluginState.OFF);
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setPluginState(android.webkit.WebSettingsClassic.PluginState)
-     */
-    @Override
-    public synchronized void setPluginState(PluginState state) {
-        if (mPluginState != state) {
-            mPluginState = state;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setPluginsPath(java.lang.String)
-     */
-    @Override
-    @Deprecated
-    public synchronized void setPluginsPath(String pluginsPath) {
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDatabasePath(java.lang.String)
-     */
-    @Override
-    public synchronized void setDatabasePath(String databasePath) {
-        if (databasePath != null && !mDatabasePathHasBeenSet) {
-            mDatabasePath = databasePath;
-            mDatabasePathHasBeenSet = true;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setGeolocationDatabasePath(java.lang.String)
-     */
-    @Override
-    public synchronized void setGeolocationDatabasePath(String databasePath) {
-        if (databasePath != null
-                && !databasePath.equals(mGeolocationDatabasePath)) {
-            mGeolocationDatabasePath = databasePath;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAppCacheEnabled(boolean)
-     */
-    @Override
-    public synchronized void setAppCacheEnabled(boolean flag) {
-        if (mAppCacheEnabled != flag) {
-            mAppCacheEnabled = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAppCachePath(java.lang.String)
-     */
-    @Override
-    public synchronized void setAppCachePath(String path) {
-        // We test for a valid path and for repeated setting on the native
-        // side, but we can avoid syncing in some simple cases. 
-        if (mAppCachePath == null && path != null && !path.isEmpty()) {
-            mAppCachePath = path;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setAppCacheMaxSize(long)
-     */
-    @Override
-    public synchronized void setAppCacheMaxSize(long appCacheMaxSize) {
-        if (appCacheMaxSize != mAppCacheMaxSize) {
-            mAppCacheMaxSize = appCacheMaxSize;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDatabaseEnabled(boolean)
-     */
-    @Override
-    public synchronized void setDatabaseEnabled(boolean flag) {
-       if (mDatabaseEnabled != flag) {
-           mDatabaseEnabled = flag;
-           postSync();
-       }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDomStorageEnabled(boolean)
-     */
-    @Override
-    public synchronized void setDomStorageEnabled(boolean flag) {
-       if (mDomStorageEnabled != flag) {
-           mDomStorageEnabled = flag;
-           postSync();
-       }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDomStorageEnabled()
-     */
-    @Override
-    public synchronized boolean getDomStorageEnabled() {
-       return mDomStorageEnabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDatabasePath()
-     */
-    @Override
-    public synchronized String getDatabasePath() {
-        return mDatabasePath;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDatabaseEnabled()
-     */
-    @Override
-    public synchronized boolean getDatabaseEnabled() {
-        return mDatabaseEnabled;
-    }
-
-    /**
-     * Tell the WebView to enable WebWorkers API.
-     * @param flag True if the WebView should enable WebWorkers.
-     * Note that this flag only affects V8. JSC does not have
-     * an equivalent setting.
-     */
-    public synchronized void setWorkersEnabled(boolean flag) {
-        if (mWorkersEnabled != flag) {
-            mWorkersEnabled = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setGeolocationEnabled(boolean)
-     */
-    @Override
-    public synchronized void setGeolocationEnabled(boolean flag) {
-        if (mGeolocationEnabled != flag) {
-            mGeolocationEnabled = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * Sets whether XSS Auditor is enabled.
-     * Only used by LayoutTestController.
-     * @param flag Whether XSS Auditor should be enabled.
-     */
-    public synchronized void setXSSAuditorEnabled(boolean flag) {
-        if (mXSSAuditorEnabled != flag) {
-            mXSSAuditorEnabled = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * Enables/disables HTML5 link "prefetch" parameter.
-     */
-    public synchronized void setLinkPrefetchEnabled(boolean flag) {
-        if (mLinkPrefetchEnabled != flag) {
-            mLinkPrefetchEnabled = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getJavaScriptEnabled()
-     */
-    @Override
-    public synchronized boolean getJavaScriptEnabled() {
-        return mJavaScriptEnabled;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getAllowUniversalFileAccessFromFileURLs
-     */
-    @Override
-    public synchronized boolean getAllowUniversalAccessFromFileURLs() {
-        return mAllowUniversalAccessFromFileURLs;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getAllowFileAccessFromFileURLs
-     */
-    @Override
-    public synchronized boolean getAllowFileAccessFromFileURLs() {
-        return mAllowFileAccessFromFileURLs;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getPluginsEnabled()
-     */
-    @Override
-    @Deprecated
-    public synchronized boolean getPluginsEnabled() {
-        return mPluginState == PluginState.ON;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getPluginState()
-     */
-    @Override
-    public synchronized PluginState getPluginState() {
-        return mPluginState;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getPluginsPath()
-     */
-    @Override
-    @Deprecated
-    public synchronized String getPluginsPath() {
-        return "";
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setJavaScriptCanOpenWindowsAutomatically(boolean)
-     */
-    @Override
-    public synchronized void setJavaScriptCanOpenWindowsAutomatically(
-            boolean flag) {
-        if (mJavaScriptCanOpenWindowsAutomatically != flag) {
-            mJavaScriptCanOpenWindowsAutomatically = flag;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getJavaScriptCanOpenWindowsAutomatically()
-     */
-    @Override
-    public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() {
-        return mJavaScriptCanOpenWindowsAutomatically;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setDefaultTextEncodingName(java.lang.String)
-     */
-    @Override
-    public synchronized void setDefaultTextEncodingName(String encoding) {
-        if (encoding != null && !encoding.equals(mDefaultTextEncoding)) {
-            mDefaultTextEncoding = encoding;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getDefaultTextEncodingName()
-     */
-    @Override
-    public synchronized String getDefaultTextEncodingName() {
-        return mDefaultTextEncoding;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setUserAgentString(java.lang.String)
-     */
-    @Override
-    public synchronized void setUserAgentString(String ua) {
-        if (ua == null || ua.length() == 0) {
-            synchronized(sLockForLocaleSettings) {
-                Locale currentLocale = Locale.getDefault();
-                if (!sLocale.equals(currentLocale)) {
-                    sLocale = currentLocale;
-                    mAcceptLanguage = getCurrentAcceptLanguage();
-                }
-            }
-            ua = getCurrentUserAgent();
-            mUseDefaultUserAgent = true;
-        } else  {
-            mUseDefaultUserAgent = false;
-        }
-
-        if (!ua.equals(mUserAgent)) {
-            mUserAgent = ua;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getUserAgentString()
-     */
-    @Override
-    public synchronized String getUserAgentString() {
-        if (DESKTOP_USERAGENT.equals(mUserAgent) ||
-                IPHONE_USERAGENT.equals(mUserAgent) ||
-                !mUseDefaultUserAgent) {
-            return mUserAgent;
-        }
-
-        boolean doPostSync = false;
-        synchronized(sLockForLocaleSettings) {
-            Locale currentLocale = Locale.getDefault();
-            if (!sLocale.equals(currentLocale)) {
-                sLocale = currentLocale;
-                mUserAgent = getCurrentUserAgent();
-                mAcceptLanguage = getCurrentAcceptLanguage();
-                doPostSync = true;
-            }
-        }
-        if (doPostSync) {
-            postSync();
-        }
-        return mUserAgent;
-    }
-
-    /* package api to grab the Accept Language string. */
-    /*package*/ synchronized String getAcceptLanguage() {
-        synchronized(sLockForLocaleSettings) {
-            Locale currentLocale = Locale.getDefault();
-            if (!sLocale.equals(currentLocale)) {
-                sLocale = currentLocale;
-                mAcceptLanguage = getCurrentAcceptLanguage();
-            }
-        }
-        return mAcceptLanguage;
-    }
-
-    /* package */ boolean isNarrowColumnLayout() {
-        return getLayoutAlgorithm() == LayoutAlgorithm.NARROW_COLUMNS;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setNeedInitialFocus(boolean)
-     */
-    @Override
-    public void setNeedInitialFocus(boolean flag) {
-        if (mNeedInitialFocus != flag) {
-            mNeedInitialFocus = flag;
-        }
-    }
-
-    /* Package api to get the choice whether it needs to set initial focus. */
-    /* package */ boolean getNeedInitialFocus() {
-        return mNeedInitialFocus;
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setRenderPriority(android.webkit.WebSettingsClassic.RenderPriority)
-     */
-    @Override
-    public synchronized void setRenderPriority(RenderPriority priority) {
-        if (mRenderPriority != priority) {
-            mRenderPriority = priority;
-            mEventHandler.sendMessage(Message.obtain(null,
-                    EventHandler.PRIORITY));
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#setCacheMode(int)
-     */
-    @Override
-    public void setCacheMode(int mode) {
-        if (mode != mOverrideCacheMode) {
-            mOverrideCacheMode = mode;
-            postSync();
-        }
-    }
-
-    /**
-     * @see android.webkit.WebSettings#getCacheMode()
-     */
-    @Override
-    public int getCacheMode() {
-        return mOverrideCacheMode;
-    }
-
-    /**
-     * If set, webkit alternately shrinks and expands images viewed outside
-     * 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.
-     */
-    public void setShrinksStandaloneImagesToFit(boolean shrink) {
-        if (mShrinksStandaloneImagesToFit != shrink) {
-            mShrinksStandaloneImagesToFit = shrink;
-            postSync();
-        }
-     }
-
-    /**
-     * 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.
-     */
-    public void setMaximumDecodedImageSize(long size) {
-        if (mMaximumDecodedImageSize != size) {
-            mMaximumDecodedImageSize = size;
-            postSync();
-        }
-    }
-
-    /**
-     * Returns whether to use fixed viewport.  Use fixed viewport
-     * whenever wide viewport is on.
-     */
-    /* package */ boolean getUseFixedViewport() {
-        return getUseWideViewPort();
-    }
-
-    /**
-     * Returns whether private browsing is enabled.
-     */
-    /* package */ boolean isPrivateBrowsingEnabled() {
-        return mPrivateBrowsingEnabled;
-    }
-
-    /**
-     * Sets whether private browsing is enabled.
-     * @param flag Whether private browsing should be enabled.
-     */
-    /* package */ synchronized void setPrivateBrowsingEnabled(boolean flag) {
-        if (mPrivateBrowsingEnabled != flag) {
-            mPrivateBrowsingEnabled = flag;
-
-            // AutoFill is dependant on private browsing being enabled so
-            // reset it to take account of the new value of mPrivateBrowsingEnabled.
-            setAutoFillEnabled(mAutoFillEnabled);
-
-            postSync();
-        }
-    }
-
-    /**
-     * Returns whether the viewport metatag can disable zooming
-     */
-    public boolean forceUserScalable() {
-        return mForceUserScalable;
-    }
-
-    /**
-     * Sets whether viewport metatag can disable zooming.
-     * @param flag Whether or not to forceably enable user scalable.
-     */
-    public synchronized void setForceUserScalable(boolean flag) {
-        mForceUserScalable = flag;
-    }
-
-    synchronized void setSyntheticLinksEnabled(boolean flag) {
-        if (mSyntheticLinksEnabled != flag) {
-            mSyntheticLinksEnabled = flag;
-            postSync();
-        }
-    }
-
-    public synchronized void setAutoFillEnabled(boolean enabled) {
-        // AutoFill is always disabled in private browsing mode.
-        boolean autoFillEnabled = enabled && !mPrivateBrowsingEnabled;
-        if (mAutoFillEnabled != autoFillEnabled) {
-            mAutoFillEnabled = autoFillEnabled;
-            postSync();
-        }
-    }
-
-    public synchronized boolean getAutoFillEnabled() {
-        return mAutoFillEnabled;
-    }
-
-    public synchronized void setAutoFillProfile(AutoFillProfile profile) {
-        if (mAutoFillProfile != profile) {
-            mAutoFillProfile = profile;
-            postSync();
-        }
-    }
-
-    public synchronized AutoFillProfile getAutoFillProfile() {
-        return mAutoFillProfile;
-    }
-
-    int getDoubleTapToastCount() {
-        return mDoubleTapToastCount;
-    }
-
-    void setDoubleTapToastCount(int count) {
-        if (mDoubleTapToastCount != count) {
-            mDoubleTapToastCount = count;
-            // write the settings in the non-UI thread
-            mEventHandler.sendMessage(Message.obtain(null,
-                    EventHandler.SET_DOUBLE_TAP_TOAST_COUNT));
-        }
-    }
-
-    public void setProperty(String key, String value) {
-        if (mWebView.nativeSetProperty(key, value)) {
-            mWebView.invalidate();
-        }
-    }
-
-    public String getProperty(String key) {
-        return mWebView.nativeGetProperty(key);
-    }
-
-    /**
-     * Transfer messages from the queue to the new WebCoreThread. Called from
-     * WebCore thread.
-     */
-    /*package*/
-    synchronized void syncSettingsAndCreateHandler(BrowserFrame frame) {
-        mBrowserFrame = frame;
-        if (DebugFlags.WEB_SETTINGS) {
-            junit.framework.Assert.assertTrue(frame.mNativeFrame != 0);
-        }
-
-        SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE,
-                Context.MODE_PRIVATE);
-        if (mDoubleTapToastCount > 0) {
-            mDoubleTapToastCount = sp.getInt(DOUBLE_TAP_TOAST_COUNT,
-                    mDoubleTapToastCount);
-        }
-        nativeSync(frame.mNativeFrame);
-        mSyncPending = false;
-        mEventHandler.createHandler();
-    }
-
-    /**
-     * Let the Settings object know that our owner is being destroyed.
-     */
-    /*package*/
-    synchronized void onDestroyed() {
-    }
-
-    private int pin(int size) {
-        // FIXME: 72 is just an arbitrary max text size value.
-        if (size < 1) {
-            return 1;
-        } else if (size > 72) {
-            return 72;
-        }
-        return size;
-    }
-
-    /* Post a SYNC message to handle syncing the native settings. */
-    private synchronized void postSync() {
-        // Only post if a sync is not pending
-        if (!mSyncPending) {
-            mSyncPending = mEventHandler.sendMessage(
-                    Message.obtain(null, EventHandler.SYNC));
-        }
-    }
-
-    // Synchronize the native and java settings.
-    private native void nativeSync(int nativeFrame);
-}
diff --git a/core/java/android/webkit/WebStorageClassic.java b/core/java/android/webkit/WebStorageClassic.java
deleted file mode 100644
index 62de5e6..0000000
--- a/core/java/android/webkit/WebStorageClassic.java
+++ /dev/null
@@ -1,352 +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;
-
-import android.os.Handler;
-import android.os.Message;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/** @hide */
-public class WebStorageClassic extends WebStorage {
-    // Global instance of a WebStorage
-    private static WebStorageClassic sWebStorage;
-
-    // Message ids
-    static final int UPDATE = 0;
-    static final int SET_QUOTA_ORIGIN = 1;
-    static final int DELETE_ORIGIN = 2;
-    static final int DELETE_ALL = 3;
-    static final int GET_ORIGINS = 4;
-    static final int GET_USAGE_ORIGIN = 5;
-    static final int GET_QUOTA_ORIGIN = 6;
-
-    // Message ids on the UI thread
-    static final int RETURN_ORIGINS = 0;
-    static final int RETURN_USAGE_ORIGIN = 1;
-    static final int RETURN_QUOTA_ORIGIN = 2;
-
-    private static final String ORIGINS = "origins";
-    private static final String ORIGIN = "origin";
-    private static final String CALLBACK = "callback";
-    private static final String USAGE = "usage";
-    private static final String QUOTA = "quota";
-
-    private Map <String, Origin> mOrigins;
-
-    private Handler mHandler = null;
-    private Handler mUIHandler = null;
-
-    /**
-     * @hide
-     * Message handler, UI side
-     * @hide
-     */
-    public void createUIHandler() {
-        if (mUIHandler == null) {
-            mUIHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case RETURN_ORIGINS: {
-                            Map values = (Map) msg.obj;
-                            Map origins = (Map) values.get(ORIGINS);
-                            ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
-                            callback.onReceiveValue(origins);
-                            } break;
-
-                        case RETURN_USAGE_ORIGIN: {
-                            Map values = (Map) msg.obj;
-                            ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
-                            callback.onReceiveValue((Long)values.get(USAGE));
-                            } break;
-
-                        case RETURN_QUOTA_ORIGIN: {
-                            Map values = (Map) msg.obj;
-                            ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
-                            callback.onReceiveValue((Long)values.get(QUOTA));
-                            } break;
-                    }
-                }
-            };
-        }
-    }
-
-    /**
-     * Message handler, WebCore side
-     * @hide
-     */
-    public synchronized void createHandler() {
-        if (mHandler == null) {
-            mHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case SET_QUOTA_ORIGIN: {
-                            Origin website = (Origin) msg.obj;
-                            nativeSetQuotaForOrigin(website.getOrigin(),
-                                                    website.getQuota());
-                            } break;
-
-                        case DELETE_ORIGIN: {
-                            Origin website = (Origin) msg.obj;
-                            nativeDeleteOrigin(website.getOrigin());
-                            } break;
-
-                        case DELETE_ALL:
-                            nativeDeleteAllData();
-                            break;
-
-                        case GET_ORIGINS: {
-                            syncValues();
-                            ValueCallback callback = (ValueCallback) msg.obj;
-                            Map origins = new HashMap(mOrigins);
-                            Map values = new HashMap<String, Object>();
-                            values.put(CALLBACK, callback);
-                            values.put(ORIGINS, origins);
-                            postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
-                            } break;
-
-                        case GET_USAGE_ORIGIN: {
-                            syncValues();
-                            Map values = (Map) msg.obj;
-                            String origin = (String) values.get(ORIGIN);
-                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
-                            Origin website = mOrigins.get(origin);
-                            Map retValues = new HashMap<String, Object>();
-                            retValues.put(CALLBACK, callback);
-                            if (website != null) {
-                                long usage = website.getUsage();
-                                retValues.put(USAGE, new Long(usage));
-                            }
-                            postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
-                            } break;
-
-                        case GET_QUOTA_ORIGIN: {
-                            syncValues();
-                            Map values = (Map) msg.obj;
-                            String origin = (String) values.get(ORIGIN);
-                            ValueCallback callback = (ValueCallback) values.get(CALLBACK);
-                            Origin website = mOrigins.get(origin);
-                            Map retValues = new HashMap<String, Object>();
-                            retValues.put(CALLBACK, callback);
-                            if (website != null) {
-                                long quota = website.getQuota();
-                                retValues.put(QUOTA, new Long(quota));
-                            }
-                            postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
-                            } break;
-
-                        case UPDATE:
-                            syncValues();
-                            break;
-                    }
-                }
-            };
-        }
-    }
-
-    /*
-     * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
-     * we need to get the values from WebCore, but we cannot block while doing so
-     * as we used to do, as this could result in a full deadlock (other WebCore
-     * messages received while we are still blocked here, see http://b/2127737).
-     *
-     * We have to do everything asynchronously, by providing a callback function.
-     * We post a message on the WebCore thread (mHandler) that will get the result
-     * from WebCore, and we post it back on the UI thread (using mUIHandler).
-     * We can then use the callback function to return the value.
-     */
-
-    @Override
-    public void getOrigins(ValueCallback<Map> callback) {
-        if (callback != null) {
-            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-                syncValues();
-                callback.onReceiveValue(mOrigins);
-            } else {
-                postMessage(Message.obtain(null, GET_ORIGINS, callback));
-            }
-        }
-    }
-
-    /**
-     * Returns a list of origins having a database
-     * should only be called from WebViewCore.
-     */
-    Collection<Origin> getOriginsSync() {
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            update();
-            return mOrigins.values();
-        }
-        return null;
-    }
-
-    @Override
-    public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
-        if (callback == null) {
-            return;
-        }
-        if (origin == null) {
-            callback.onReceiveValue(null);
-            return;
-        }
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            syncValues();
-            Origin website = mOrigins.get(origin);
-            callback.onReceiveValue(new Long(website.getUsage()));
-        } else {
-            HashMap values = new HashMap<String, Object>();
-            values.put(ORIGIN, origin);
-            values.put(CALLBACK, callback);
-            postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
-        }
-    }
-
-    @Override
-    public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
-        if (callback == null) {
-            return;
-        }
-        if (origin == null) {
-            callback.onReceiveValue(null);
-            return;
-        }
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            syncValues();
-            Origin website = mOrigins.get(origin);
-            callback.onReceiveValue(new Long(website.getUsage()));
-        } else {
-            HashMap values = new HashMap<String, Object>();
-            values.put(ORIGIN, origin);
-            values.put(CALLBACK, callback);
-            postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
-        }
-    }
-
-    @Override
-    public void setQuotaForOrigin(String origin, long quota) {
-        if (origin != null) {
-            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-                nativeSetQuotaForOrigin(origin, quota);
-            } else {
-                postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
-                    new Origin(origin, quota)));
-            }
-        }
-    }
-
-    @Override
-    public void deleteOrigin(String origin) {
-        if (origin != null) {
-            if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-                nativeDeleteOrigin(origin);
-            } else {
-                postMessage(Message.obtain(null, DELETE_ORIGIN,
-                    new Origin(origin)));
-            }
-        }
-    }
-
-    @Override
-    public void deleteAllData() {
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            nativeDeleteAllData();
-        } else {
-            postMessage(Message.obtain(null, DELETE_ALL));
-        }
-    }
-
-    /**
-     * Sets the maximum size of the ApplicationCache.
-     * This should only ever be called on the WebKit thread.
-     * Not part of the base-class API: this is only used by dump render tree.
-     */
-    public void setAppCacheMaximumSize(long size) {
-        nativeSetAppCacheMaximumSize(size);
-    }
-
-    /**
-     * Utility function to send a message to our handler
-     */
-    private synchronized void postMessage(Message msg) {
-        if (mHandler != null) {
-            mHandler.sendMessage(msg);
-        }
-    }
-
-    /**
-     * Utility function to send a message to the handler on the UI thread
-     */
-    private void postUIMessage(Message msg) {
-        if (mUIHandler != null) {
-            mUIHandler.sendMessage(msg);
-        }
-    }
-
-    /**
-     * Get the singleton instance of this class.
-     * @return The singleton {@link WebStorage} instance.
-     */
-    public static WebStorageClassic getInstance() {
-      if (sWebStorage == null) {
-          sWebStorage = new WebStorageClassic();
-      }
-      return sWebStorage;
-    }
-
-    /**
-     * @hide
-     * Post a Sync request
-     */
-    public void update() {
-        if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
-            syncValues();
-        } else {
-            postMessage(Message.obtain(null, UPDATE));
-        }
-    }
-
-    /**
-     * Run on the WebCore thread
-     * set the local values with the current ones
-     */
-    private void syncValues() {
-        Set<String> tmp = nativeGetOrigins();
-        mOrigins = new HashMap<String, Origin>();
-        for (String origin : tmp) {
-            Origin website = new Origin(origin,
-                                 nativeGetQuotaForOrigin(origin),
-                                 nativeGetUsageForOrigin(origin));
-            mOrigins.put(origin, website);
-        }
-    }
-
-    WebStorageClassic() {}
-
-    // Native functions
-    private static native Set nativeGetOrigins();
-    private static native long nativeGetUsageForOrigin(String origin);
-    private static native long nativeGetQuotaForOrigin(String origin);
-    private static native void nativeSetQuotaForOrigin(String origin, long quota);
-    private static native void nativeDeleteOrigin(String origin);
-    private static native void nativeDeleteAllData();
-    private static native void nativeSetAppCacheMaximumSize(long size);
-}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
deleted file mode 100644
index 911073d..0000000
--- a/core/java/android/webkit/WebTextView.java
+++ /dev/null
@@ -1,53 +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.
- */
-
-package android.webkit;
-
-import android.util.Log;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-// TODO: Move these to a better place.
-/* package */ abstract class WebTextView {
-
-    private static final String LOGTAG = "WebTextView";
-
-    // Types used with setType.  Keep in sync with CachedInput.h
-    static final int NORMAL_TEXT_FIELD = 0;
-    static final int TEXT_AREA = 1;
-    static final int PASSWORD = 2;
-    static final int SEARCH = 3;
-    static final int EMAIL = 4;
-    static final int NUMBER = 5;
-    static final int TELEPHONE = 6;
-    static final int URL = 7;
-
-    static final int FORM_NOT_AUTOFILLABLE = -1;
-
-    static String urlForAutoCompleteData(String urlString) {
-        // Remove any fragment or query string.
-        URL url = null;
-        try {
-            url = new URL(urlString);
-        } catch (MalformedURLException e) {
-            Log.e(LOGTAG, "Unable to parse URL "+url);
-        }
-
-        return url != null ? url.getProtocol() + "://" + url.getHost() + url.getPath() : null;
-    }
-
-}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 93c3147..9fc8779 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -701,7 +701,6 @@
      */
     @Deprecated
     public static void enablePlatformNotifications() {
-        checkThread();
         getFactory().getStatics().setPlatformNotificationsEnabled(true);
     }
 
@@ -714,7 +713,6 @@
      */
     @Deprecated
     public static void disablePlatformNotifications() {
-        checkThread();
         getFactory().getStatics().setPlatformNotificationsEnabled(false);
     }
 
@@ -724,7 +722,6 @@
      * @hide
      */
     public static void freeMemoryForTests() {
-        checkThread();
         getFactory().getStatics().freeMemoryForTests();
     }
 
@@ -1740,7 +1737,6 @@
      * @param enabled whether to enable web contents debugging
      */
     public static void setWebContentsDebuggingEnabled(boolean enabled) {
-        checkThread();
         getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
     }
 
@@ -1753,7 +1749,6 @@
      */
     @Deprecated
     public static synchronized PluginList getPluginList() {
-        checkThread();
         return new PluginList();
     }
 
@@ -2107,13 +2102,18 @@
         return WebViewFactory.getProvider();
     }
 
-    private static void checkThread() {
-        if (Looper.myLooper() != Looper.getMainLooper()) {
+    private final Looper mWebViewThread = Looper.myLooper();
+
+    private void checkThread() {
+        // Ignore mWebViewThread == null because this can be called during in the super class
+        // constructor, before this class's own constructor has even started.
+        if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
             Throwable throwable = new Throwable(
-                    "Warning: A WebView method was called on thread '" +
+                    "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.");
+                    "All WebView methods must be called on the same thread. " +
+                    "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
+                    ", FYI main Looper is " + Looper.getMainLooper() + ")");
             Log.w(LOGTAG, Log.getStackTraceString(throwable));
             StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
 
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
deleted file mode 100644
index 97a0d24..0000000
--- a/core/java/android/webkit/WebViewClassic.java
+++ /dev/null
@@ -1,8814 +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;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.animation.ObjectAnimator;
-import android.annotation.Widget;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.database.DataSetObserver;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.DrawFilter;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Picture;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.net.Proxy;
-import android.net.ProxyProperties;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Selection;
-import android.text.TextUtils;
-import android.util.AndroidRuntimeException;
-import android.util.DisplayMetrics;
-import android.util.EventLog;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
-import android.view.HardwareCanvas;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.SoundEffectConstants;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.webkit.WebView.HitTestResult;
-import android.webkit.WebView.PictureListener;
-import android.webkit.WebViewCore.DrawData;
-import android.webkit.WebViewCore.EventHub;
-import android.webkit.WebViewCore.TextFieldInitData;
-import android.webkit.WebViewCore.TextSelectionData;
-import android.webkit.WebViewCore.WebKitHitTest;
-import android.widget.AbsoluteLayout;
-import android.widget.Adapter;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.CheckedTextView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.OverScroller;
-import android.widget.PopupWindow;
-import android.widget.Scroller;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import junit.framework.Assert;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URLDecoder;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-/**
- * Implements a backend provider for the {@link WebView} public API.
- * @hide
- */
-// 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
-@SuppressWarnings("deprecation")
-public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate,
-        WebViewProvider.ViewDelegate {
-    /**
-     * InputConnection used for ContentEditable. This captures changes
-     * to the text and sends them either as key strokes or text changes.
-     */
-    class WebViewInputConnection extends BaseInputConnection {
-        // Used for mapping characters to keys typed.
-        private KeyCharacterMap mKeyCharacterMap;
-        private boolean mIsKeySentByMe;
-        private int mInputType;
-        private int mImeOptions;
-        private String mHint;
-        private int mMaxLength;
-        private boolean mIsAutoFillable;
-        private boolean mIsAutoCompleteEnabled;
-        private String mName;
-        private int mBatchLevel;
-
-        public WebViewInputConnection() {
-            super(mWebView, true);
-        }
-
-        public void setAutoFillable(int queryId) {
-            mIsAutoFillable = getSettings().getAutoFillEnabled()
-                    && (queryId != WebTextView.FORM_NOT_AUTOFILLABLE);
-            int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
-            if (variation != EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD
-                    && (mIsAutoFillable || mIsAutoCompleteEnabled)) {
-                if (mName != null && mName.length() > 0) {
-                    requestFormData(mName, mFieldPointer, mIsAutoFillable,
-                            mIsAutoCompleteEnabled);
-                }
-            }
-        }
-
-        @Override
-        public boolean beginBatchEdit() {
-            if (mBatchLevel == 0) {
-                beginTextBatch();
-            }
-            mBatchLevel++;
-            return false;
-        }
-
-        @Override
-        public boolean endBatchEdit() {
-            mBatchLevel--;
-            if (mBatchLevel == 0) {
-                commitTextBatch();
-            }
-            return false;
-        }
-
-        public boolean getIsAutoFillable() {
-            return mIsAutoFillable;
-        }
-
-        @Override
-        public boolean sendKeyEvent(KeyEvent event) {
-            // Some IMEs send key events directly using sendKeyEvents.
-            // WebViewInputConnection should treat these as text changes.
-            if (!mIsKeySentByMe) {
-                if (event.getAction() == KeyEvent.ACTION_UP) {
-                    if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
-                        return deleteSurroundingText(1, 0);
-                    } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
-                        return deleteSurroundingText(0, 1);
-                    } else if (event.getUnicodeChar() != 0){
-                        String newComposingText =
-                                Character.toString((char)event.getUnicodeChar());
-                        return commitText(newComposingText, 1);
-                    }
-                } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
-                        (event.getKeyCode() == KeyEvent.KEYCODE_DEL
-                        || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
-                        || event.getUnicodeChar() != 0)) {
-                    return true; // only act on action_down
-                }
-            }
-            return super.sendKeyEvent(event);
-        }
-
-        public void setTextAndKeepSelection(CharSequence text) {
-            Editable editable = getEditable();
-            int selectionStart = Selection.getSelectionStart(editable);
-            int selectionEnd = Selection.getSelectionEnd(editable);
-            text = limitReplaceTextByMaxLength(text, editable.length());
-            editable.replace(0, editable.length(), text);
-            restartInput();
-            // Keep the previous selection.
-            selectionStart = Math.min(selectionStart, editable.length());
-            selectionEnd = Math.min(selectionEnd, editable.length());
-            setSelection(selectionStart, selectionEnd);
-            finishComposingText();
-        }
-
-        public void replaceSelection(CharSequence text) {
-            Editable editable = getEditable();
-            int selectionStart = Selection.getSelectionStart(editable);
-            int selectionEnd = Selection.getSelectionEnd(editable);
-            text = limitReplaceTextByMaxLength(text, selectionEnd - selectionStart);
-            setNewText(selectionStart, selectionEnd, text);
-            editable.replace(selectionStart, selectionEnd, text);
-            restartInput();
-            // Move caret to the end of the new text
-            int newCaret = selectionStart + text.length();
-            setSelection(newCaret, newCaret);
-        }
-
-        @Override
-        public boolean setComposingText(CharSequence text, int newCursorPosition) {
-            Editable editable = getEditable();
-            int start = getComposingSpanStart(editable);
-            int end = getComposingSpanEnd(editable);
-            if (start < 0 || end < 0) {
-                start = Selection.getSelectionStart(editable);
-                end = Selection.getSelectionEnd(editable);
-            }
-            if (end < start) {
-                int temp = end;
-                end = start;
-                start = temp;
-            }
-            CharSequence limitedText = limitReplaceTextByMaxLength(text, end - start);
-            setNewText(start, end, limitedText);
-            if (limitedText != text) {
-                newCursorPosition -= text.length() - limitedText.length();
-            }
-            super.setComposingText(limitedText, newCursorPosition);
-            updateSelection();
-            if (limitedText != text) {
-                int lastCaret = start + limitedText.length();
-                finishComposingText();
-                setSelection(lastCaret, lastCaret);
-            }
-            return true;
-        }
-
-        @Override
-        public boolean commitText(CharSequence text, int newCursorPosition) {
-            setComposingText(text, newCursorPosition);
-            finishComposingText();
-            return true;
-        }
-
-        @Override
-        public boolean deleteSurroundingText(int leftLength, int rightLength) {
-            // This code is from BaseInputConnection#deleteSurroundText.
-            // We have to delete the same text in webkit.
-            Editable content = getEditable();
-            int a = Selection.getSelectionStart(content);
-            int b = Selection.getSelectionEnd(content);
-
-            if (a > b) {
-                int tmp = a;
-                a = b;
-                b = tmp;
-            }
-
-            int ca = getComposingSpanStart(content);
-            int cb = getComposingSpanEnd(content);
-            if (cb < ca) {
-                int tmp = ca;
-                ca = cb;
-                cb = tmp;
-            }
-            if (ca != -1 && cb != -1) {
-                if (ca < a) a = ca;
-                if (cb > b) b = cb;
-            }
-
-            int endDelete = Math.min(content.length(), b + rightLength);
-            if (endDelete > b) {
-                setNewText(b, endDelete, "");
-            }
-            int startDelete = Math.max(0, a - leftLength);
-            if (startDelete < a) {
-                setNewText(startDelete, a, "");
-            }
-            return super.deleteSurroundingText(leftLength, rightLength);
-        }
-
-        @Override
-        public boolean performEditorAction(int editorAction) {
-
-            boolean handled = true;
-            switch (editorAction) {
-            case EditorInfo.IME_ACTION_NEXT:
-                mWebView.requestFocus(View.FOCUS_FORWARD);
-                break;
-            case EditorInfo.IME_ACTION_PREVIOUS:
-                mWebView.requestFocus(View.FOCUS_BACKWARD);
-                break;
-            case EditorInfo.IME_ACTION_DONE:
-                WebViewClassic.this.hideSoftKeyboard();
-                break;
-            case EditorInfo.IME_ACTION_GO:
-            case EditorInfo.IME_ACTION_SEARCH:
-                WebViewClassic.this.hideSoftKeyboard();
-                String text = getEditable().toString();
-                passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_DOWN,
-                        KeyEvent.KEYCODE_ENTER));
-                passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_UP,
-                        KeyEvent.KEYCODE_ENTER));
-                break;
-
-            default:
-                handled = super.performEditorAction(editorAction);
-                break;
-            }
-
-            return handled;
-        }
-
-        public void initEditorInfo(WebViewCore.TextFieldInitData initData) {
-            int type = initData.mType;
-            int inputType = InputType.TYPE_CLASS_TEXT
-                    | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
-            int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
-                    | EditorInfo.IME_FLAG_NO_FULLSCREEN;
-            if (!initData.mIsSpellCheckEnabled) {
-                inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
-            }
-            if (WebTextView.TEXT_AREA != type) {
-                if (initData.mIsTextFieldNext) {
-                    imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
-                }
-                if (initData.mIsTextFieldPrev) {
-                    imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
-                }
-            }
-            int action = EditorInfo.IME_ACTION_GO;
-            switch (type) {
-                case WebTextView.NORMAL_TEXT_FIELD:
-                    break;
-                case WebTextView.TEXT_AREA:
-                    inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
-                            | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
-                            | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
-                    action = EditorInfo.IME_ACTION_NONE;
-                    break;
-                case WebTextView.PASSWORD:
-                    inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
-                    break;
-                case WebTextView.SEARCH:
-                    action = EditorInfo.IME_ACTION_SEARCH;
-                    break;
-                case WebTextView.EMAIL:
-                    // inputType needs to be overwritten because of the different text variation.
-                    inputType = InputType.TYPE_CLASS_TEXT
-                            | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
-                    break;
-                case WebTextView.NUMBER:
-                    // inputType needs to be overwritten because of the different class.
-                    inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL
-                            | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
-                    // Number and telephone do not have both a Tab key and an
-                    // action, so set the action to NEXT
-                    break;
-                case WebTextView.TELEPHONE:
-                    // inputType needs to be overwritten because of the different class.
-                    inputType = InputType.TYPE_CLASS_PHONE;
-                    break;
-                case WebTextView.URL:
-                    // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
-                    // exclude it for now.
-                    inputType |= InputType.TYPE_TEXT_VARIATION_URI;
-                    break;
-                default:
-                    break;
-            }
-            imeOptions |= action;
-            mHint = initData.mLabel;
-            mInputType = inputType;
-            mImeOptions = imeOptions;
-            mMaxLength = initData.mMaxLength;
-            mIsAutoCompleteEnabled = initData.mIsAutoCompleteEnabled;
-            mName = initData.mName;
-            mAutoCompletePopup.clearAdapter();
-        }
-
-        public void setupEditorInfo(EditorInfo outAttrs) {
-            outAttrs.inputType = mInputType;
-            outAttrs.imeOptions = mImeOptions;
-            outAttrs.hintText = mHint;
-            outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
-
-            Editable editable = getEditable();
-            int selectionStart = Selection.getSelectionStart(editable);
-            int selectionEnd = Selection.getSelectionEnd(editable);
-            if (selectionStart < 0 || selectionEnd < 0) {
-                selectionStart = editable.length();
-                selectionEnd = selectionStart;
-            }
-            outAttrs.initialSelStart = selectionStart;
-            outAttrs.initialSelEnd = selectionEnd;
-        }
-
-        @Override
-        public boolean setSelection(int start, int end) {
-            boolean result = super.setSelection(start, end);
-            updateSelection();
-            return result;
-        }
-
-        @Override
-        public boolean setComposingRegion(int start, int end) {
-            boolean result = super.setComposingRegion(start, end);
-            updateSelection();
-            return result;
-        }
-
-        /**
-         * Send the selection and composing spans to the IME.
-         */
-        private void updateSelection() {
-            Editable editable = getEditable();
-            int selectionStart = Selection.getSelectionStart(editable);
-            int selectionEnd = Selection.getSelectionEnd(editable);
-            int composingStart = getComposingSpanStart(editable);
-            int composingEnd = getComposingSpanEnd(editable);
-            InputMethodManager imm = InputMethodManager.peekInstance();
-            if (imm != null) {
-                imm.updateSelection(mWebView, selectionStart, selectionEnd,
-                        composingStart, composingEnd);
-            }
-        }
-
-        /**
-         * Sends a text change to webkit indirectly. If it is a single-
-         * character add or delete, it sends it as a key stroke. If it cannot
-         * be represented as a key stroke, it sends it as a field change.
-         * @param start The start offset (inclusive) of the text being changed.
-         * @param end The end offset (exclusive) of the text being changed.
-         * @param text The new text to replace the changed text.
-         */
-        private void setNewText(int start, int end, CharSequence text) {
-            mIsKeySentByMe = true;
-            Editable editable = getEditable();
-            CharSequence original = editable.subSequence(start, end);
-            boolean isCharacterAdd = false;
-            boolean isCharacterDelete = false;
-            int textLength = text.length();
-            int originalLength = original.length();
-            int selectionStart = Selection.getSelectionStart(editable);
-            int selectionEnd = Selection.getSelectionEnd(editable);
-            if (selectionStart == selectionEnd) {
-                if (textLength > originalLength) {
-                    isCharacterAdd = (textLength == originalLength + 1)
-                            && TextUtils.regionMatches(text, 0, original, 0,
-                                    originalLength);
-                } else if (originalLength > textLength) {
-                    isCharacterDelete = (textLength == originalLength - 1)
-                            && TextUtils.regionMatches(text, 0, original, 0,
-                                    textLength);
-                }
-            }
-            if (isCharacterAdd) {
-                sendCharacter(text.charAt(textLength - 1));
-            } else if (isCharacterDelete) {
-                sendKey(KeyEvent.KEYCODE_DEL);
-            } else if ((textLength != originalLength) ||
-                    !TextUtils.regionMatches(text, 0, original, 0,
-                            textLength)) {
-                // Send a message so that key strokes and text replacement
-                // do not come out of order.
-                Message replaceMessage = mPrivateHandler.obtainMessage(
-                        REPLACE_TEXT, start,  end, text.toString());
-                mPrivateHandler.sendMessage(replaceMessage);
-            }
-            if (mAutoCompletePopup != null) {
-                StringBuilder newText = new StringBuilder();
-                newText.append(editable.subSequence(0, start));
-                newText.append(text);
-                newText.append(editable.subSequence(end, editable.length()));
-                mAutoCompletePopup.setText(newText.toString());
-            }
-            mIsKeySentByMe = false;
-        }
-
-        /**
-         * Send a single character to the WebView as a key down and up event.
-         * @param c The character to be sent.
-         */
-        private void sendCharacter(char c) {
-            if (mKeyCharacterMap == null) {
-                mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-            }
-            char[] chars = new char[1];
-            chars[0] = c;
-            KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
-            if (events != null) {
-                for (KeyEvent event : events) {
-                    sendKeyEvent(event);
-                }
-            } else {
-                Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0);
-                mPrivateHandler.sendMessage(msg);
-            }
-        }
-
-        /**
-         * Send a key event for a specific key code, not a standard
-         * unicode character.
-         * @param keyCode The key code to send.
-         */
-        private void sendKey(int keyCode) {
-            long eventTime = SystemClock.uptimeMillis();
-            sendKeyEvent(new KeyEvent(eventTime, eventTime,
-                    KeyEvent.ACTION_DOWN, keyCode, 0, 0,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
-                    KeyEvent.FLAG_SOFT_KEYBOARD));
-            sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
-                    KeyEvent.ACTION_UP, keyCode, 0, 0,
-                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
-                    KeyEvent.FLAG_SOFT_KEYBOARD));
-        }
-
-        private CharSequence limitReplaceTextByMaxLength(CharSequence text,
-                int numReplaced) {
-            if (mMaxLength > 0) {
-                Editable editable = getEditable();
-                int maxReplace = mMaxLength - editable.length() + numReplaced;
-                if (maxReplace < text.length()) {
-                    maxReplace = Math.max(maxReplace, 0);
-                    // New length is greater than the maximum. trim it down.
-                    text = text.subSequence(0, maxReplace);
-                }
-            }
-            return text;
-        }
-
-        private void restartInput() {
-            InputMethodManager imm = InputMethodManager.peekInstance();
-            if (imm != null) {
-                // Since the text has changed, do not allow the IME to replace the
-                // existing text as though it were a completion.
-                imm.restartInput(mWebView);
-            }
-        }
-    }
-
-    private class PastePopupWindow extends PopupWindow implements View.OnClickListener {
-        private ViewGroup mContentView;
-        private TextView mPasteTextView;
-
-        public PastePopupWindow() {
-            super(mContext, null,
-                    com.android.internal.R.attr.textSelectHandleWindowStyle);
-            setClippingEnabled(true);
-            LinearLayout linearLayout = new LinearLayout(mContext);
-            linearLayout.setOrientation(LinearLayout.HORIZONTAL);
-            mContentView = linearLayout;
-            mContentView.setBackgroundResource(
-                    com.android.internal.R.drawable.text_edit_paste_window);
-
-            LayoutInflater inflater = (LayoutInflater)mContext.
-                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-            ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            mPasteTextView = (TextView) inflater.inflate(
-                    com.android.internal.R.layout.text_edit_action_popup_text, null);
-            mPasteTextView.setLayoutParams(wrapContent);
-            mContentView.addView(mPasteTextView);
-            mPasteTextView.setText(com.android.internal.R.string.paste);
-            mPasteTextView.setOnClickListener(this);
-            this.setContentView(mContentView);
-        }
-
-        public void show(Point cursorBottom, Point cursorTop,
-                int windowLeft, int windowTop) {
-            measureContent();
-
-            int width = mContentView.getMeasuredWidth();
-            int height = mContentView.getMeasuredHeight();
-            int y = cursorTop.y - height;
-            int x = cursorTop.x - (width / 2);
-            if (y < windowTop) {
-                // There's not enough room vertically, move it below the
-                // handle.
-                ensureSelectionHandles();
-                y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight();
-                x = cursorBottom.x - (width / 2);
-            }
-            if (x < windowLeft) {
-                x = windowLeft;
-            }
-            if (!isShowing()) {
-                showAtLocation(mWebView, Gravity.NO_GRAVITY, x, y);
-            }
-            update(x, y, width, height);
-        }
-
-        public void hide() {
-            dismiss();
-        }
-
-        @Override
-        public void onClick(View view) {
-            pasteFromClipboard();
-            selectionDone();
-        }
-
-        protected void measureContent() {
-            final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
-            mContentView.measure(
-                    View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
-                            View.MeasureSpec.AT_MOST),
-                    View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
-                            View.MeasureSpec.AT_MOST));
-        }
-    }
-
-    // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
-    // the screen all-the-time. Good for profiling our drawing code
-    static private final boolean AUTO_REDRAW_HACK = false;
-
-    // The rate at which edit text is scrolled in content pixels per millisecond
-    static private final float TEXT_SCROLL_RATE = 0.01f;
-
-    // The presumed scroll rate for the first scroll of edit text
-    static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
-
-    // Buffer pixels of the caret rectangle when moving edit text into view
-    // after resize.
-    static private final int EDIT_RECT_BUFFER = 10;
-
-    static private final long SELECTION_HANDLE_ANIMATION_MS = 150;
-
-    // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
-    private boolean mAutoRedraw;
-
-    // Reference to the AlertDialog displayed by InvokeListBox.
-    // It's used to dismiss the dialog in destroy if not done before.
-    private AlertDialog mListBoxDialog = null;
-
-    // Reference to the save password dialog so it can be dimissed in
-    // destroy if not done before.
-    private AlertDialog mSavePasswordDialog = null;
-
-    static final String LOGTAG = "webview";
-
-    private ZoomManager mZoomManager;
-
-    private final Rect mInvScreenRect = new Rect();
-    private final Rect mScreenRect = new Rect();
-    private final RectF mVisibleContentRect = new RectF();
-    private boolean mIsWebViewVisible = true;
-    WebViewInputConnection mInputConnection = null;
-    private int mFieldPointer;
-    private PastePopupWindow mPasteWindow;
-    private AutoCompletePopup mAutoCompletePopup;
-    Rect mEditTextContentBounds = new Rect();
-    Rect mEditTextContent = new Rect();
-    int mEditTextLayerId;
-    boolean mIsEditingText = false;
-    ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
-    boolean mIsBatchingTextChanges = false;
-    private long mLastEditScroll = 0;
-
-    private static class OnTrimMemoryListener implements ComponentCallbacks2 {
-        private static OnTrimMemoryListener sInstance = null;
-
-        static void init(Context c) {
-            if (sInstance == null) {
-                sInstance = new OnTrimMemoryListener(c.getApplicationContext());
-            }
-        }
-
-        private OnTrimMemoryListener(Context c) {
-            c.registerComponentCallbacks(this);
-        }
-
-        @Override
-        public void onConfigurationChanged(Configuration newConfig) {
-            // Ignore
-        }
-
-        @Override
-        public void onLowMemory() {
-            // Ignore
-        }
-
-        @Override
-        public void onTrimMemory(int level) {
-            if (DebugFlags.WEB_VIEW) {
-                Log.d("WebView", "onTrimMemory: " + level);
-            }
-            // When framework reset EGL context during high memory pressure, all
-            // the existing GL resources for the html5 video will be destroyed
-            // at native side.
-            // Here we just need to clean up the Surface Texture which is static.
-            if (level > TRIM_MEMORY_UI_HIDDEN) {
-                HTML5VideoInline.cleanupSurfaceTexture();
-                HTML5VideoView.release();
-            }
-            WebViewClassic.nativeOnTrimMemory(level);
-        }
-    }
-
-    // A final CallbackProxy shared by WebViewCore and BrowserFrame.
-    private CallbackProxy mCallbackProxy;
-
-    private WebViewDatabaseClassic mDatabase;
-
-    // SSL certificate for the main top-level page (if secure)
-    private SslCertificate mCertificate;
-
-    // Native WebView pointer that is 0 until the native object has been
-    // created.
-    private int mNativeClass;
-    // This would be final but it needs to be set to null when the WebView is
-    // destroyed.
-    private WebViewCore mWebViewCore;
-    // Handler for dispatching UI messages.
-    /* package */ final Handler mPrivateHandler = new PrivateHandler();
-    // Used to ignore changes to webkit text that arrives to the UI side after
-    // more key events.
-    private int mTextGeneration;
-
-    /* package */ void incrementTextGeneration() { mTextGeneration++; }
-
-    // Used by WebViewCore to create child views.
-    /* package */ ViewManager mViewManager;
-
-    // Used to display in full screen mode
-    PluginFullScreenHolder mFullScreenHolder;
-
-    /**
-     * Position of the last touch event in pixels.
-     * Use integer to prevent loss of dragging delta calculation accuracy;
-     * which was done in float and converted to integer, and resulted in gradual
-     * and compounding touch position and view dragging mismatch.
-     */
-    private int mLastTouchX;
-    private int mLastTouchY;
-    private int mStartTouchX;
-    private int mStartTouchY;
-    private float mAverageAngle;
-
-    /**
-     * Time of the last touch event.
-     */
-    private long mLastTouchTime;
-
-    /**
-     * Time of the last time sending touch event to WebViewCore
-     */
-    private long mLastSentTouchTime;
-
-    /**
-     * The minimum elapsed time before sending another ACTION_MOVE event to
-     * WebViewCore. This really should be tuned for each type of the devices.
-     * For example in Google Map api test case, it takes Dream device at least
-     * 150ms to do a full cycle in the WebViewCore by processing a touch event,
-     * triggering the layout and drawing the picture. While the same process
-     * takes 60+ms on the current high speed device. If we make
-     * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
-     * to WebViewCore queue and the real layout and draw events will be pushed
-     * to further, which slows down the refresh rate. Choose 50 to favor the
-     * current high speed devices. For Dream like devices, 100 is a better
-     * choice. Maybe make this in the buildspec later.
-     * (Update 12/14/2010: changed to 0 since current device should be able to
-     * handle the raw events and Map team voted to have the raw events too.
-     */
-    private static final int TOUCH_SENT_INTERVAL = 0;
-    private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
-
-    /**
-     * Helper class to get velocity for fling
-     */
-    VelocityTracker mVelocityTracker;
-    private int mMaximumFling;
-    private float mLastVelocity;
-    private float mLastVelX;
-    private float mLastVelY;
-
-    // The id of the native layer being scrolled.
-    private int mCurrentScrollingLayerId;
-    private Rect mScrollingLayerRect = new Rect();
-
-    // only trigger accelerated fling if the new velocity is at least
-    // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
-    private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
-
-    /**
-     * Touch mode
-     * TODO: Some of this is now unnecessary as it is handled by
-     * WebInputTouchDispatcher (such as click, long press, and double tap).
-     */
-    private int mTouchMode = TOUCH_DONE_MODE;
-    private static final int TOUCH_INIT_MODE = 1;
-    private static final int TOUCH_DRAG_START_MODE = 2;
-    private static final int TOUCH_DRAG_MODE = 3;
-    private static final int TOUCH_SHORTPRESS_START_MODE = 4;
-    private static final int TOUCH_SHORTPRESS_MODE = 5;
-    private static final int TOUCH_DOUBLE_TAP_MODE = 6;
-    private static final int TOUCH_DONE_MODE = 7;
-    private static final int TOUCH_PINCH_DRAG = 8;
-    private static final int TOUCH_DRAG_LAYER_MODE = 9;
-    private static final int TOUCH_DRAG_TEXT_MODE = 10;
-
-    // true when the touch movement exceeds the slop
-    private boolean mConfirmMove;
-    private boolean mTouchInEditText;
-
-    // Whether or not to draw the cursor ring.
-    private boolean mDrawCursorRing = true;
-
-    // true if onPause has been called (and not onResume)
-    private boolean mIsPaused;
-
-    private HitTestResult mInitialHitTestResult;
-    private WebKitHitTest mFocusedNode;
-
-    /**
-     * Customizable constant
-     */
-    // pre-computed square of ViewConfiguration.getScaledTouchSlop()
-    private int mTouchSlopSquare;
-    // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
-    private int mDoubleTapSlopSquare;
-    // pre-computed density adjusted navigation slop
-    private int mNavSlop;
-    // This should be ViewConfiguration.getTapTimeout()
-    // But system time out is 100ms, which is too short for the browser.
-    // In the browser, if it switches out of tap too soon, jump tap won't work.
-    // In addition, a double tap on a trackpad will always have a duration of
-    // 300ms, so this value must be at least that (otherwise we will timeout the
-    // first tap and convert it to a long press).
-    private static final int TAP_TIMEOUT = 300;
-    // This should be ViewConfiguration.getLongPressTimeout()
-    // But system time out is 500ms, which is too short for the browser.
-    // With a short timeout, it's difficult to treat trigger a short press.
-    private static final int LONG_PRESS_TIMEOUT = 1000;
-    // needed to avoid flinging after a pause of no movement
-    private static final int MIN_FLING_TIME = 250;
-    // draw unfiltered after drag is held without movement
-    private static final int MOTIONLESS_TIME = 100;
-    // The amount of content to overlap between two screens when going through
-    // pages with the space bar, in pixels.
-    private static final int PAGE_SCROLL_OVERLAP = 24;
-
-    /**
-     * These prevent calling requestLayout if either dimension is fixed. This
-     * depends on the layout parameters and the measure specs.
-     */
-    boolean mWidthCanMeasure;
-    boolean mHeightCanMeasure;
-
-    // Remember the last dimensions we sent to the native side so we can avoid
-    // sending the same dimensions more than once.
-    int mLastWidthSent;
-    int mLastHeightSent;
-    // Since view height sent to webkit could be fixed to avoid relayout, this
-    // value records the last sent actual view height.
-    int mLastActualHeightSent;
-
-    private int mContentWidth;   // cache of value from WebViewCore
-    private int mContentHeight;  // cache of value from WebViewCore
-
-    // Need to have the separate control for horizontal and vertical scrollbar
-    // style than the View's single scrollbar style
-    private boolean mOverlayHorizontalScrollbar = true;
-    private boolean mOverlayVerticalScrollbar = false;
-
-    // our standard speed. this way small distances will be traversed in less
-    // time than large distances, but we cap the duration, so that very large
-    // distances won't take too long to get there.
-    private static final int STD_SPEED = 480;  // pixels per second
-    // time for the longest scroll animation
-    private static final int MAX_DURATION = 750;   // milliseconds
-
-    // Used by OverScrollGlow
-    OverScroller mScroller;
-    Scroller mEditTextScroller;
-
-    private boolean mInOverScrollMode = false;
-    private static Paint mOverScrollBackground;
-    private static Paint mOverScrollBorder;
-
-    private boolean mWrapContent;
-    private static final int MOTIONLESS_FALSE           = 0;
-    private static final int MOTIONLESS_PENDING         = 1;
-    private static final int MOTIONLESS_TRUE            = 2;
-    private static final int MOTIONLESS_IGNORE          = 3;
-    private int mHeldMotionless;
-
-    // Lazily-instantiated instance for injecting accessibility.
-    private AccessibilityInjector mAccessibilityInjector;
-
-    /**
-     * How long the caret handle will last without being touched.
-     */
-    private static final long CARET_HANDLE_STAMINA_MS = 3000;
-
-    private Drawable mSelectHandleLeft;
-    private Drawable mSelectHandleRight;
-    private Drawable mSelectHandleCenter;
-    private Point mSelectOffset;
-    private Point mSelectCursorBase = new Point();
-    private Rect mSelectHandleBaseBounds = new Rect();
-    private int mSelectCursorBaseLayerId;
-    private QuadF mSelectCursorBaseTextQuad = new QuadF();
-    private Point mSelectCursorExtent = new Point();
-    private Rect mSelectHandleExtentBounds = new Rect();
-    private int mSelectCursorExtentLayerId;
-    private QuadF mSelectCursorExtentTextQuad = new QuadF();
-    private Point mSelectDraggingCursor;
-    private QuadF mSelectDraggingTextQuad;
-    private boolean mIsCaretSelection;
-    static final int HANDLE_ID_BASE = 0;
-    static final int HANDLE_ID_EXTENT = 1;
-
-    // the color used to highlight the touch rectangles
-    static final int HIGHLIGHT_COLOR = 0x6633b5e5;
-    // the region indicating where the user touched on the screen
-    private Region mTouchHighlightRegion = new Region();
-    // the paint for the touch highlight
-    private Paint mTouchHightlightPaint = new Paint();
-    // debug only
-    private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
-    private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
-    private Paint mTouchCrossHairColor;
-    private int mTouchHighlightX;
-    private int mTouchHighlightY;
-    private boolean mShowTapHighlight;
-
-    // Basically this proxy is used to tell the Video to update layer tree at
-    // SetBaseLayer time and to pause when WebView paused.
-    private HTML5VideoViewProxy mHTML5VideoViewProxy;
-
-    // If we are using a set picture, don't send view updates to webkit
-    private boolean mBlockWebkitViewMessages = false;
-
-    // cached value used to determine if we need to switch drawing models
-    private boolean mHardwareAccelSkia = false;
-
-    /*
-     * Private message ids
-     */
-    private static final int REMEMBER_PASSWORD          = 1;
-    private static final int NEVER_REMEMBER_PASSWORD    = 2;
-    private static final int SWITCH_TO_SHORTPRESS       = 3;
-    private static final int SWITCH_TO_LONGPRESS        = 4;
-    private static final int RELEASE_SINGLE_TAP         = 5;
-    private static final int REQUEST_FORM_DATA          = 6;
-    private static final int DRAG_HELD_MOTIONLESS       = 8;
-    private static final int PREVENT_DEFAULT_TIMEOUT    = 10;
-    private static final int SCROLL_SELECT_TEXT         = 11;
-
-
-    private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
-    private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT;
-
-    /*
-     * Package message ids
-     */
-    static final int SCROLL_TO_MSG_ID                   = 101;
-    static final int NEW_PICTURE_MSG_ID                 = 105;
-    static final int WEBCORE_INITIALIZED_MSG_ID         = 107;
-    static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 108;
-    static final int UPDATE_ZOOM_RANGE                  = 109;
-    static final int TAKE_FOCUS                         = 110;
-    static final int CLEAR_TEXT_ENTRY                   = 111;
-    static final int UPDATE_TEXT_SELECTION_MSG_ID       = 112;
-    static final int SHOW_RECT_MSG_ID                   = 113;
-    static final int LONG_PRESS_CENTER                  = 114;
-    static final int PREVENT_TOUCH_ID                   = 115;
-    static final int WEBCORE_NEED_TOUCH_EVENTS          = 116;
-    // obj=Rect in doc coordinates
-    static final int INVAL_RECT_MSG_ID                  = 117;
-    static final int REQUEST_KEYBOARD                   = 118;
-    static final int SHOW_FULLSCREEN                    = 120;
-    static final int HIDE_FULLSCREEN                    = 121;
-    static final int UPDATE_MATCH_COUNT                 = 126;
-    static final int CENTER_FIT_RECT                    = 127;
-    static final int SET_SCROLLBAR_MODES                = 129;
-    static final int HIT_TEST_RESULT                    = 130;
-    static final int SAVE_WEBARCHIVE_FINISHED           = 131;
-    static final int SET_AUTOFILLABLE                   = 132;
-    static final int AUTOFILL_COMPLETE                  = 133;
-    static final int SCREEN_ON                          = 134;
-    static final int UPDATE_ZOOM_DENSITY                = 135;
-    static final int EXIT_FULLSCREEN_VIDEO              = 136;
-    static final int COPY_TO_CLIPBOARD                  = 137;
-    static final int INIT_EDIT_FIELD                    = 138;
-    static final int REPLACE_TEXT                       = 139;
-    static final int CLEAR_CARET_HANDLE                 = 140;
-    static final int KEY_PRESS                          = 141;
-    static final int RELOCATE_AUTO_COMPLETE_POPUP       = 142;
-    static final int FOCUS_NODE_CHANGED                 = 143;
-    static final int AUTOFILL_FORM                      = 144;
-    static final int SCROLL_EDIT_TEXT                   = 145;
-    static final int EDIT_TEXT_SIZE_CHANGED             = 146;
-    static final int SHOW_CARET_HANDLE                  = 147;
-    static final int UPDATE_CONTENT_BOUNDS              = 148;
-    static final int SCROLL_HANDLE_INTO_VIEW            = 149;
-
-    private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
-    private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
-
-    static final String[] HandlerPrivateDebugString = {
-        "REMEMBER_PASSWORD", //              = 1;
-        "NEVER_REMEMBER_PASSWORD", //        = 2;
-        "SWITCH_TO_SHORTPRESS", //           = 3;
-        "SWITCH_TO_LONGPRESS", //            = 4;
-        "RELEASE_SINGLE_TAP", //             = 5;
-        "REQUEST_FORM_DATA", //              = 6;
-        "RESUME_WEBCORE_PRIORITY", //        = 7;
-        "DRAG_HELD_MOTIONLESS", //           = 8;
-        "", //             = 9;
-        "PREVENT_DEFAULT_TIMEOUT", //        = 10;
-        "SCROLL_SELECT_TEXT" //              = 11;
-    };
-
-    static final String[] HandlerPackageDebugString = {
-        "SCROLL_TO_MSG_ID", //               = 101;
-        "102", //                            = 102;
-        "103", //                            = 103;
-        "104", //                            = 104;
-        "NEW_PICTURE_MSG_ID", //             = 105;
-        "UPDATE_TEXT_ENTRY_MSG_ID", //       = 106;
-        "WEBCORE_INITIALIZED_MSG_ID", //     = 107;
-        "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 108;
-        "UPDATE_ZOOM_RANGE", //              = 109;
-        "UNHANDLED_NAV_KEY", //              = 110;
-        "CLEAR_TEXT_ENTRY", //               = 111;
-        "UPDATE_TEXT_SELECTION_MSG_ID", //   = 112;
-        "SHOW_RECT_MSG_ID", //               = 113;
-        "LONG_PRESS_CENTER", //              = 114;
-        "PREVENT_TOUCH_ID", //               = 115;
-        "WEBCORE_NEED_TOUCH_EVENTS", //      = 116;
-        "INVAL_RECT_MSG_ID", //              = 117;
-        "REQUEST_KEYBOARD", //               = 118;
-        "DO_MOTION_UP", //                   = 119;
-        "SHOW_FULLSCREEN", //                = 120;
-        "HIDE_FULLSCREEN", //                = 121;
-        "DOM_FOCUS_CHANGED", //              = 122;
-        "REPLACE_BASE_CONTENT", //           = 123;
-        "RETURN_LABEL", //                   = 125;
-        "UPDATE_MATCH_COUNT", //             = 126;
-        "CENTER_FIT_RECT", //                = 127;
-        "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
-        "SET_SCROLLBAR_MODES", //            = 129;
-        "SELECTION_STRING_CHANGED", //       = 130;
-        "SET_TOUCH_HIGHLIGHT_RECTS", //      = 131;
-        "SAVE_WEBARCHIVE_FINISHED", //       = 132;
-        "SET_AUTOFILLABLE", //               = 133;
-        "AUTOFILL_COMPLETE", //              = 134;
-        "SELECT_AT", //                      = 135;
-        "SCREEN_ON", //                      = 136;
-        "ENTER_FULLSCREEN_VIDEO", //         = 137;
-        "UPDATE_SELECTION", //               = 138;
-        "UPDATE_ZOOM_DENSITY" //             = 139;
-    };
-
-    // If the site doesn't use the viewport meta tag to specify the viewport,
-    // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
-    static final int DEFAULT_VIEWPORT_WIDTH = 980;
-
-    // normally we try to fit the content to the minimum preferred width
-    // calculated by the Webkit. To avoid the bad behavior when some site's
-    // minimum preferred width keeps growing when changing the viewport width or
-    // the minimum preferred width is huge, an upper limit is needed.
-    static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
-
-    // initial scale in percent. 0 means using default.
-    private int mInitialScaleInPercent = 0;
-
-    // Whether or not a scroll event should be sent to webkit.  This is only set
-    // to false when restoring the scroll position.
-    private boolean mSendScrollEvent = true;
-
-    private int mSnapScrollMode = SNAP_NONE;
-    private static final int SNAP_NONE = 0;
-    private static final int SNAP_LOCK = 1; // not a separate state
-    private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
-    private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
-    private boolean mSnapPositive;
-
-    // keep these in sync with their counterparts in WebView.cpp
-    private static final int DRAW_EXTRAS_NONE = 0;
-    private static final int DRAW_EXTRAS_SELECTION = 1;
-    private static final int DRAW_EXTRAS_CURSOR_RING = 2;
-
-    // keep this in sync with WebCore:ScrollbarMode in WebKit
-    private static final int SCROLLBAR_AUTO = 0;
-    private static final int SCROLLBAR_ALWAYSOFF = 1;
-    // as we auto fade scrollbar, this is ignored.
-    private static final int SCROLLBAR_ALWAYSON = 2;
-    private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
-    private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
-
-    /**
-     * Max distance to overscroll by in pixels.
-     * This how far content can be pulled beyond its normal bounds by the user.
-     */
-    private int mOverscrollDistance;
-
-    /**
-     * Max distance to overfling by in pixels.
-     * This is how far flinged content can move beyond the end of its normal bounds.
-     */
-    private int mOverflingDistance;
-
-    private OverScrollGlow mOverScrollGlow;
-
-    // Used to match key downs and key ups
-    private Vector<Integer> mKeysPressed;
-
-    /* package */ static boolean mLogEvent = true;
-
-    // for event log
-    private long mLastTouchUpTime = 0;
-
-    private WebViewCore.AutoFillData mAutoFillData;
-
-    private static boolean sNotificationsEnabled = true;
-
-    /**
-     * URI scheme for telephone number
-     */
-    public static final String SCHEME_TEL = "tel:";
-    /**
-     * URI scheme for email address
-     */
-    public static final String SCHEME_MAILTO = "mailto:";
-    /**
-     * URI scheme for map address
-     */
-    public static final String SCHEME_GEO = "geo:0,0?q=";
-
-    private int mBackgroundColor = Color.WHITE;
-
-    private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second
-    private int mAutoScrollX = 0;
-    private int mAutoScrollY = 0;
-    private int mMinAutoScrollX = 0;
-    private int mMaxAutoScrollX = 0;
-    private int mMinAutoScrollY = 0;
-    private int mMaxAutoScrollY = 0;
-    private Rect mScrollingLayerBounds = new Rect();
-    private boolean mSentAutoScrollMessage = false;
-
-    // used for serializing asynchronously handled touch events.
-    private WebViewInputDispatcher mInputDispatcher;
-
-    // Used to track whether picture updating was paused due to a window focus change.
-    private boolean mPictureUpdatePausedForFocusChange = false;
-
-    // Used to notify listeners of a new picture.
-    private PictureListener mPictureListener;
-
-    // Used to notify listeners about find-on-page results.
-    private WebView.FindListener mFindListener;
-
-    // Used to prevent resending save password message
-    private Message mResumeMsg;
-
-    /**
-     * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
-     */
-    static class FocusNodeHref {
-        static final String TITLE = "title";
-        static final String URL = "url";
-        static final String SRC = "src";
-    }
-
-    public WebViewClassic(WebView webView, WebView.PrivateAccess privateAccess) {
-        mWebView = webView;
-        mWebViewPrivate = privateAccess;
-        mContext = webView.getContext();
-    }
-
-    /**
-     * See {@link WebViewProvider#init(Map, boolean)}
-     */
-    @Override
-    public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        Context context = mContext;
-
-        // Used by the chrome stack to find application paths
-        JniUtil.setContext(context);
-
-        mCallbackProxy = new CallbackProxy(context, this);
-        mViewManager = new ViewManager(this);
-        L10nUtils.setApplicationContext(context.getApplicationContext());
-        mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces);
-        mDatabase = WebViewDatabaseClassic.getInstance(context);
-        mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
-        mZoomManager = new ZoomManager(this, mCallbackProxy);
-
-        /* The init method must follow the creation of certain member variables,
-         * such as the mZoomManager.
-         */
-        init();
-        setupPackageListener(context);
-        setupProxyListener(context);
-        setupTrustStorageListener(context);
-        updateMultiTouchSupport(context);
-
-        if (privateBrowsing) {
-            startPrivateBrowsing();
-        }
-
-        mAutoFillData = new WebViewCore.AutoFillData();
-        mEditTextScroller = new Scroller(context);
-
-        // Calculate channel distance
-        calculateChannelDistance(context);
-    }
-
-    /**
-     * Calculate sChannelDistance based on the screen information.
-     * @param context A Context object used to access application assets.
-     */
-    private void calculateChannelDistance(Context context) {
-        // The channel distance is adjusted for density and screen size
-        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
-        final double screenSize = Math.hypot((double)(metrics.widthPixels/metrics.densityDpi),
-                (double)(metrics.heightPixels/metrics.densityDpi));
-        if (screenSize < 3.0) {
-            sChannelDistance = 16;
-        } else if (screenSize < 5.0) {
-            sChannelDistance = 22;
-        } else if (screenSize < 7.0) {
-            sChannelDistance = 28;
-        } else {
-            sChannelDistance = 34;
-        }
-        sChannelDistance = (int)(sChannelDistance * metrics.density);
-        if (sChannelDistance < 16) sChannelDistance = 16;
-
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "sChannelDistance : " + sChannelDistance
-                    + ", density : " + metrics.density
-                    + ", screenSize : " + screenSize
-                    + ", metrics.heightPixels : " + metrics.heightPixels
-                    + ", metrics.widthPixels : " + metrics.widthPixels
-                    + ", metrics.densityDpi : " + metrics.densityDpi);
-        }
-    }
-
-    // WebViewProvider bindings
-
-    static class Factory implements WebViewFactoryProvider,  WebViewFactoryProvider.Statics {
-        Factory() {
-            // Touch JniUtil and WebViewCore in case this is being called from
-            // WebViewFactory.Preloader, to ensure that the JNI libraries that they use are
-            // preloaded in the zygote.
-            try {
-                Class.forName("android.webkit.JniUtil");
-                Class.forName("android.webkit.WebViewCore");
-            } catch (ClassNotFoundException e) {
-                Log.e(LOGTAG, "failed to load JNI libraries");
-                throw new AndroidRuntimeException(e);
-            }
-        }
-
-        @Override
-        public String findAddress(String addr) {
-            return WebViewClassic.findAddress(addr);
-        }
-        @Override
-        public void setPlatformNotificationsEnabled(boolean enable) {
-            if (enable) {
-                WebViewClassic.enablePlatformNotifications();
-            } else {
-                WebViewClassic.disablePlatformNotifications();
-            }
-        }
-        @Override
-        public void freeMemoryForTests() {
-            // noop.
-        }
-
-        @Override
-        public Statics getStatics() { return this; }
-
-        @Override
-        public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
-            return new WebViewClassic(webView, privateAccess);
-        }
-
-        @Override
-        public GeolocationPermissions getGeolocationPermissions() {
-            return GeolocationPermissionsClassic.getInstance();
-        }
-
-        @Override
-        public CookieManager getCookieManager() {
-            return CookieManagerClassic.getInstance();
-        }
-
-        @Override
-        public WebIconDatabase getWebIconDatabase() {
-            return WebIconDatabaseClassic.getInstance();
-        }
-
-        @Override
-        public WebStorage getWebStorage() {
-            return WebStorageClassic.getInstance();
-        }
-
-        @Override
-        public WebViewDatabase getWebViewDatabase(Context context) {
-            return WebViewDatabaseClassic.getInstance(context);
-        }
-
-        @Override
-        public String getDefaultUserAgent(Context context) {
-            return WebSettingsClassic.getDefaultUserAgentForLocale(context,
-                    Locale.getDefault());
-        }
-
-        @Override
-        public void setWebContentsDebuggingEnabled(boolean enable) {
-            // no-op for WebViewClassic.
-        }
-    }
-
-    private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
-        switch (eventType) {
-        case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
-            HitTestResult hitTest = getHitTestResult();
-            if (hitTest != null) {
-                mWebView.performLongClick();
-            }
-            break;
-        case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
-            mZoomManager.handleDoubleTap(event.getX(), event.getY());
-            break;
-        case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
-            onHandleUiTouchEvent(event);
-            break;
-        case WebViewInputDispatcher.EVENT_TYPE_CLICK:
-            if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
-                mWebView.playSoundEffect(SoundEffectConstants.CLICK);
-                overrideLoading(mFocusedNode.mIntentUrl);
-            }
-            break;
-        }
-    }
-
-    private void onHandleUiTouchEvent(MotionEvent ev) {
-        final ScaleGestureDetector detector =
-                mZoomManager.getScaleGestureDetector();
-
-        int action = ev.getActionMasked();
-        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
-        final boolean configChanged =
-            action == MotionEvent.ACTION_POINTER_UP ||
-            action == MotionEvent.ACTION_POINTER_DOWN;
-        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
-
-        // Determine focal point
-        float sumX = 0, sumY = 0;
-        final int count = ev.getPointerCount();
-        for (int i = 0; i < count; i++) {
-            if (skipIndex == i) continue;
-            sumX += ev.getX(i);
-            sumY += ev.getY(i);
-        }
-        final int div = pointerUp ? count - 1 : count;
-        float x = sumX / div;
-        float y = sumY / div;
-
-        if (configChanged) {
-            mLastTouchX = Math.round(x);
-            mLastTouchY = Math.round(y);
-            mLastTouchTime = ev.getEventTime();
-            mWebView.cancelLongPress();
-            mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-        }
-
-        if (detector != null) {
-            detector.onTouchEvent(ev);
-            if (detector.isInProgress()) {
-                mLastTouchTime = ev.getEventTime();
-
-                if (!mZoomManager.supportsPanDuringZoom()) {
-                    return;
-                }
-                mTouchMode = TOUCH_DRAG_MODE;
-                if (mVelocityTracker == null) {
-                    mVelocityTracker = VelocityTracker.obtain();
-                }
-            }
-        }
-
-        if (action == MotionEvent.ACTION_POINTER_DOWN) {
-            cancelTouch();
-            action = MotionEvent.ACTION_DOWN;
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            // negative x or y indicate it is on the edge, skip it.
-            if (x < 0 || y < 0) {
-                return;
-            }
-        }
-
-        handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
-    }
-
-    // The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
-    // as the first param in the WebViewClient and WebChromeClient callbacks.
-    final private WebView mWebView;
-    // Callback interface, provides priviledged access into the WebView instance.
-    final private WebView.PrivateAccess mWebViewPrivate;
-    // Cached reference to mWebView.getContext(), for convenience.
-    final private Context mContext;
-
-    /**
-     * @return The webview proxy that this classic webview is bound to.
-     */
-    public WebView getWebView() {
-        return mWebView;
-    }
-
-    @Override
-    public ViewDelegate getViewDelegate() {
-        return this;
-    }
-
-    @Override
-    public ScrollDelegate getScrollDelegate() {
-        return this;
-    }
-
-    public static WebViewClassic fromWebView(WebView webView) {
-        return webView == null ? null : (WebViewClassic) webView.getWebViewProvider();
-    }
-
-    // Accessors, purely for convenience (and to reduce code churn during webview proxy migration).
-    int getScrollX() {
-        return mWebView.getScrollX();
-    }
-
-    int getScrollY() {
-        return mWebView.getScrollY();
-    }
-
-    int getWidth() {
-        return mWebView.getWidth();
-    }
-
-    int getHeight() {
-        return mWebView.getHeight();
-    }
-
-    Context getContext() {
-        return mContext;
-    }
-
-    void invalidate() {
-        mWebView.invalidate();
-    }
-
-    // Setters for the Scroll X & Y, without invoking the onScrollChanged etc code paths.
-    void setScrollXRaw(int mScrollX) {
-        mWebViewPrivate.setScrollXRaw(mScrollX);
-    }
-
-    void setScrollYRaw(int mScrollY) {
-        mWebViewPrivate.setScrollYRaw(mScrollY);
-    }
-
-    private static class TrustStorageListener extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
-                handleCertTrustChanged();
-            }
-        }
-    }
-    private static TrustStorageListener sTrustStorageListener;
-
-    /**
-     * Handles update to the trust storage.
-     */
-    private static void handleCertTrustChanged() {
-        // send a message for indicating trust storage change
-        WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null);
-    }
-
-    /*
-     * @param context This method expects this to be a valid context.
-     */
-    private static void setupTrustStorageListener(Context context) {
-        if (sTrustStorageListener != null ) {
-            return;
-        }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
-        sTrustStorageListener = new TrustStorageListener();
-        Intent current =
-            context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
-        if (current != null) {
-            handleCertTrustChanged();
-        }
-    }
-
-    private static class ProxyReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
-                handleProxyBroadcast(intent);
-            }
-        }
-    }
-
-    /*
-     * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts.
-     */
-    private static ProxyReceiver sProxyReceiver;
-
-    /*
-     * @param context This method expects this to be a valid context
-     */
-    private static synchronized void setupProxyListener(Context context) {
-        if (sProxyReceiver != null || sNotificationsEnabled == false) {
-            return;
-        }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Proxy.PROXY_CHANGE_ACTION);
-        sProxyReceiver = new ProxyReceiver();
-        Intent currentProxy = context.getApplicationContext().registerReceiver(
-                sProxyReceiver, filter);
-        if (currentProxy != null) {
-            handleProxyBroadcast(currentProxy);
-        }
-    }
-
-    /*
-     * @param context This method expects this to be a valid context
-     */
-    private static synchronized void disableProxyListener(Context context) {
-        if (sProxyReceiver == null)
-            return;
-
-        context.getApplicationContext().unregisterReceiver(sProxyReceiver);
-        sProxyReceiver = null;
-    }
-
-    private static void handleProxyBroadcast(Intent intent) {
-        ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
-        if (proxyProperties == null || proxyProperties.getHost() == null) {
-            WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, null);
-            return;
-        }
-        WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, proxyProperties);
-    }
-
-    /*
-     * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED
-     * or ACTION_PACKAGE_REMOVED.
-     */
-    private static boolean sPackageInstallationReceiverAdded = false;
-
-    /*
-     * A set of Google packages we monitor for the
-     * navigator.isApplicationInstalled() API. Add additional packages as
-     * needed.
-     */
-    private static Set<String> sGoogleApps;
-    static {
-        sGoogleApps = new HashSet<String>();
-        sGoogleApps.add("com.google.android.youtube");
-    }
-
-    private static class PackageListener extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final String packageName = intent.getData().getSchemeSpecificPart();
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-            if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
-                // if it is replacing, refreshPlugins() when adding
-                return;
-            }
-
-            if (sGoogleApps.contains(packageName)) {
-                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                    WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName);
-                } else {
-                    WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
-                }
-            }
-
-            PluginManager pm = PluginManager.getInstance(context);
-            if (pm.containsPluginPermissionAndSignatures(packageName)) {
-                pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
-            }
-        }
-    }
-
-    private void setupPackageListener(Context context) {
-
-        /*
-         * we must synchronize the instance check and the creation of the
-         * receiver to ensure that only ONE receiver exists for all WebView
-         * instances.
-         */
-        synchronized (WebViewClassic.class) {
-
-            // if the receiver already exists then we do not need to register it
-            // again
-            if (sPackageInstallationReceiverAdded) {
-                return;
-            }
-
-            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            filter.addDataScheme("package");
-            BroadcastReceiver packageListener = new PackageListener();
-            context.getApplicationContext().registerReceiver(packageListener, filter);
-            sPackageInstallationReceiverAdded = true;
-        }
-
-        // check if any of the monitored apps are already installed
-        AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
-
-            @Override
-            protected Set<String> doInBackground(Void... unused) {
-                Set<String> installedPackages = new HashSet<String>();
-                PackageManager pm = mContext.getPackageManager();
-                for (String name : sGoogleApps) {
-                    try {
-                        pm.getPackageInfo(name,
-                                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
-                        installedPackages.add(name);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        // package not found
-                    }
-                }
-                return installedPackages;
-            }
-
-            // Executes on the UI thread
-            @Override
-            protected void onPostExecute(Set<String> installedPackages) {
-                if (mWebViewCore != null) {
-                    mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
-                }
-            }
-        };
-        task.execute();
-    }
-
-    void updateMultiTouchSupport(Context context) {
-        mZoomManager.updateMultiTouchSupport(context);
-    }
-
-    void updateJavaScriptEnabled(boolean enabled) {
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().updateJavaScriptEnabled(enabled);
-        }
-    }
-
-    private void init() {
-        OnTrimMemoryListener.init(mContext);
-        mWebView.setWillNotDraw(false);
-        mWebView.setClickable(true);
-        mWebView.setLongClickable(true);
-
-        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
-        int slop = configuration.getScaledTouchSlop();
-        mTouchSlopSquare = slop * slop;
-        slop = configuration.getScaledDoubleTapSlop();
-        mDoubleTapSlopSquare = slop * slop;
-        final float density = WebViewCore.getFixedDisplayDensity(mContext);
-        // use one line height, 16 based on our current default font, for how
-        // far we allow a touch be away from the edge of a link
-        mNavSlop = (int) (16 * density);
-        mZoomManager.init(density);
-        mMaximumFling = configuration.getScaledMaximumFlingVelocity();
-
-        // Compute the inverse of the density squared.
-        DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
-
-        mOverscrollDistance = configuration.getScaledOverscrollDistance();
-        mOverflingDistance = configuration.getScaledOverflingDistance();
-
-        setScrollBarStyle(mWebViewPrivate.super_getScrollBarStyle());
-        // Initially use a size of two, since the user is likely to only hold
-        // down two keys at a time (shift + another key)
-        mKeysPressed = new Vector<Integer>(2);
-        mHTML5VideoViewProxy = null ;
-    }
-
-    @Override
-    public boolean shouldDelayChildPressedState() {
-        return true;
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (!mWebView.isEnabled()) {
-            // Only default actions are supported while disabled.
-            return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
-        }
-
-        if (getAccessibilityInjector().supportsAccessibilityAction(action)) {
-            return getAccessibilityInjector().performAccessibilityAction(action, arguments);
-        }
-
-        switch (action) {
-            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
-            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
-                final int convertedContentHeight = contentToViewY(getContentHeight());
-                final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
-                        - mWebView.getPaddingBottom();
-                final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
-                final boolean canScrollBackward = (getScrollY() > 0);
-                final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
-                if ((action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) && canScrollBackward) {
-                    mWebView.scrollBy(0, adjustedViewHeight);
-                    return true;
-                }
-                if ((action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) && canScrollForward) {
-                    mWebView.scrollBy(0, -adjustedViewHeight);
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
-    }
-
-    @Override
-    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
-      return null;
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        if (!mWebView.isEnabled()) {
-            // Only default actions are supported while disabled.
-            return;
-        }
-
-        info.setScrollable(isScrollableForAccessibility());
-
-        final int convertedContentHeight = contentToViewY(getContentHeight());
-        final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
-                - mWebView.getPaddingBottom();
-        final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
-        final boolean canScrollBackward = (getScrollY() > 0);
-        final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
-
-        if (canScrollForward) {
-            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-        }
-
-        if (canScrollForward) {
-            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-        }
-
-        getAccessibilityInjector().onInitializeAccessibilityNodeInfo(info);
-    }
-
-    @Override
-    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
-        event.setScrollable(isScrollableForAccessibility());
-        event.setScrollX(getScrollX());
-        event.setScrollY(getScrollY());
-        final int convertedContentWidth = contentToViewX(getContentWidth());
-        final int adjustedViewWidth = getWidth() - mWebView.getPaddingLeft()
-                - mWebView.getPaddingLeft();
-        event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0));
-        final int convertedContentHeight = contentToViewY(getContentHeight());
-        final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
-                - mWebView.getPaddingBottom();
-        event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
-    }
-
-    /* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
-        }
-    }
-
-    private boolean isAccessibilityInjectionEnabled() {
-        final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
-        if (!manager.isEnabled()) {
-            return false;
-        }
-
-        // Accessibility scripts should be injected only when a speaking service
-        // is enabled. This may need to change later to accommodate Braille.
-        final List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList(
-                AccessibilityServiceInfo.FEEDBACK_SPOKEN);
-        if (services.isEmpty()) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private AccessibilityInjector getAccessibilityInjector() {
-        if (mAccessibilityInjector == null) {
-            mAccessibilityInjector = new AccessibilityInjector(this);
-        }
-        return mAccessibilityInjector;
-    }
-
-    private boolean isScrollableForAccessibility() {
-        return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
-                - mWebView.getPaddingRight()
-                || contentToViewY(getContentHeight()) > getHeight() - mWebView.getPaddingTop()
-                - mWebView.getPaddingBottom());
-    }
-
-    @Override
-    public void setOverScrollMode(int mode) {
-        if (mode != View.OVER_SCROLL_NEVER) {
-            if (mOverScrollGlow == null) {
-                mOverScrollGlow = new OverScrollGlow(this);
-            }
-        } else {
-            mOverScrollGlow = null;
-        }
-    }
-
-    /* package */ void adjustDefaultZoomDensity(int zoomDensity) {
-        final float density = WebViewCore.getFixedDisplayDensity(mContext)
-                * 100 / zoomDensity;
-        updateDefaultZoomDensity(density);
-    }
-
-    /* package */ void updateDefaultZoomDensity(float density) {
-        mNavSlop = (int) (16 * density);
-        mZoomManager.updateDefaultZoomDensity(density);
-    }
-
-    /* package */ int getScaledNavSlop() {
-        return viewToContentDimension(mNavSlop);
-    }
-
-    /* package */ boolean onSavePassword(String schemePlusHost, String username,
-            String password, final Message resumeMsg) {
-        boolean rVal = false;
-        if (resumeMsg == null) {
-            // null resumeMsg implies saving password silently
-            mDatabase.setUsernamePassword(schemePlusHost, username, password);
-        } else {
-            if (mResumeMsg != null) {
-                Log.w(LOGTAG, "onSavePassword should not be called while dialog is up");
-                resumeMsg.sendToTarget();
-                return true;
-            }
-            mResumeMsg = resumeMsg;
-            final Message remember = mPrivateHandler.obtainMessage(
-                    REMEMBER_PASSWORD);
-            remember.getData().putString("host", schemePlusHost);
-            remember.getData().putString("username", username);
-            remember.getData().putString("password", password);
-            remember.obj = resumeMsg;
-
-            final Message neverRemember = mPrivateHandler.obtainMessage(
-                    NEVER_REMEMBER_PASSWORD);
-            neverRemember.getData().putString("host", schemePlusHost);
-            neverRemember.getData().putString("username", username);
-            neverRemember.getData().putString("password", password);
-            neverRemember.obj = resumeMsg;
-
-            mSavePasswordDialog = new AlertDialog.Builder(mContext)
-                    .setTitle(com.android.internal.R.string.save_password_label)
-                    .setMessage(com.android.internal.R.string.save_password_message)
-                    .setPositiveButton(com.android.internal.R.string.save_password_notnow,
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (mResumeMsg != null) {
-                                resumeMsg.sendToTarget();
-                                mResumeMsg = null;
-                            }
-                            mSavePasswordDialog = null;
-                        }
-                    })
-                    .setNeutralButton(com.android.internal.R.string.save_password_remember,
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (mResumeMsg != null) {
-                                remember.sendToTarget();
-                                mResumeMsg = null;
-                            }
-                            mSavePasswordDialog = null;
-                        }
-                    })
-                    .setNegativeButton(com.android.internal.R.string.save_password_never,
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (mResumeMsg != null) {
-                                neverRemember.sendToTarget();
-                                mResumeMsg = null;
-                            }
-                            mSavePasswordDialog = null;
-                        }
-                    })
-                    .setOnDismissListener(new DialogInterface.OnDismissListener() {
-                        @Override
-                        public void onDismiss(DialogInterface dialog) {
-                            if (mResumeMsg != null) {
-                                resumeMsg.sendToTarget();
-                                mResumeMsg = null;
-                            }
-                            mSavePasswordDialog = null;
-                        }
-                    }).show();
-            // Return true so that WebViewCore will pause while the dialog is
-            // up.
-            rVal = true;
-        }
-        return rVal;
-    }
-
-    @Override
-    public void setScrollBarStyle(int style) {
-        if (style == View.SCROLLBARS_INSIDE_INSET
-                || style == View.SCROLLBARS_OUTSIDE_INSET) {
-            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
-        } else {
-            mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
-        }
-    }
-
-    /**
-     * See {@link WebView#setHorizontalScrollbarOverlay(boolean)}
-     */
-    @Override
-    public void setHorizontalScrollbarOverlay(boolean overlay) {
-        mOverlayHorizontalScrollbar = overlay;
-    }
-
-    /**
-     * See {@link WebView#setVerticalScrollbarOverlay(boolean)
-     */
-    @Override
-    public void setVerticalScrollbarOverlay(boolean overlay) {
-        mOverlayVerticalScrollbar = overlay;
-    }
-
-    /**
-     * See {@link WebView#overlayHorizontalScrollbar()}
-     */
-    @Override
-    public boolean overlayHorizontalScrollbar() {
-        return mOverlayHorizontalScrollbar;
-    }
-
-    /**
-     * See {@link WebView#overlayVerticalScrollbar()}
-     */
-    @Override
-    public boolean overlayVerticalScrollbar() {
-        return mOverlayVerticalScrollbar;
-    }
-
-    /*
-     * Return the width of the view where the content of WebView should render
-     * to.
-     * Note: this can be called from WebCoreThread.
-     */
-    /* package */ int getViewWidth() {
-        if (!mWebView.isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
-            return getWidth();
-        } else {
-            return Math.max(0, getWidth() - mWebView.getVerticalScrollbarWidth());
-        }
-    }
-
-    // Interface to enable the browser to override title bar handling.
-    public interface TitleBarDelegate {
-        int getTitleHeight();
-        public void onSetEmbeddedTitleBar(final View title);
-    }
-
-    /**
-     * Returns the height (in pixels) of the embedded title bar (if any). Does not care about
-     * scrolling
-     */
-    protected int getTitleHeight() {
-        if (mWebView instanceof TitleBarDelegate) {
-            return ((TitleBarDelegate) mWebView).getTitleHeight();
-        }
-        return 0;
-    }
-
-    /**
-     * See {@link WebView#getVisibleTitleHeight()}
-     */
-    @Override
-    @Deprecated
-    public int getVisibleTitleHeight() {
-        // Actually, this method returns the height of the embedded title bar if one is set via the
-        // hidden setEmbeddedTitleBar method.
-        return getVisibleTitleHeightImpl();
-    }
-
-    private int getVisibleTitleHeightImpl() {
-        // need to restrict mScrollY due to over scroll
-        return Math.max(getTitleHeight() - Math.max(0, getScrollY()),
-                getOverlappingActionModeHeight());
-    }
-
-    private int mCachedOverlappingActionModeHeight = -1;
-
-    private int getOverlappingActionModeHeight() {
-        if (mFindCallback == null) {
-            return 0;
-        }
-        if (mCachedOverlappingActionModeHeight < 0) {
-            mWebView.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
-            mCachedOverlappingActionModeHeight = Math.max(0,
-                    mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top);
-        }
-        return mCachedOverlappingActionModeHeight;
-    }
-
-    /*
-     * Return the height of the view where the content of WebView should render
-     * to.  Note that this excludes mTitleBar, if there is one.
-     * Note: this can be called from WebCoreThread.
-     */
-    /* package */ int getViewHeight() {
-        return getViewHeightWithTitle() - getVisibleTitleHeightImpl();
-    }
-
-    int getViewHeightWithTitle() {
-        int height = getHeight();
-        if (mWebView.isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
-            height -= mWebViewPrivate.getHorizontalScrollbarHeight();
-        }
-        return height;
-    }
-
-    /**
-     * See {@link WebView#getCertificate()}
-     */
-    @Override
-    public SslCertificate getCertificate() {
-        return mCertificate;
-    }
-
-    /**
-     * See {@link WebView#setCertificate(SslCertificate)}
-     */
-    @Override
-    public void setCertificate(SslCertificate certificate) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "setCertificate=" + certificate);
-        }
-        // here, the certificate can be null (if the site is not secure)
-        mCertificate = certificate;
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods called by activity
-    //-------------------------------------------------------------------------
-
-    /**
-     * See {@link WebView#savePassword(String, String, String)}
-     */
-    @Override
-    public void savePassword(String host, String username, String password) {
-        mDatabase.setUsernamePassword(host, username, password);
-    }
-
-    /**
-     * See {@link WebView#setHttpAuthUsernamePassword(String, String, String, String)}
-     */
-    @Override
-    public void setHttpAuthUsernamePassword(String host, String realm,
-            String username, String password) {
-        mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
-    }
-
-    /**
-     * See {@link WebView#getHttpAuthUsernamePassword(String, String)}
-     */
-    @Override
-    public String[] getHttpAuthUsernamePassword(String host, String realm) {
-        return mDatabase.getHttpAuthUsernamePassword(host, realm);
-    }
-
-    /**
-     * Remove Find or Select ActionModes, if active.
-     */
-    private void clearActionModes() {
-        if (mSelectCallback != null) {
-            mSelectCallback.finish();
-        }
-        if (mFindCallback != null) {
-            mFindCallback.finish();
-        }
-    }
-
-    /**
-     * Called to clear state when moving from one page to another, or changing
-     * in some other way that makes elements associated with the current page
-     * (such as ActionModes) no longer relevant.
-     */
-    private void clearHelpers() {
-        hideSoftKeyboard();
-        clearActionModes();
-        dismissFullScreenMode();
-        cancelDialogs();
-    }
-
-    private void cancelDialogs() {
-        if (mListBoxDialog != null) {
-            mListBoxDialog.cancel();
-            mListBoxDialog = null;
-        }
-        if (mSavePasswordDialog != null) {
-            mSavePasswordDialog.dismiss();
-            mSavePasswordDialog = null;
-        }
-    }
-
-    /**
-     * See {@link WebView#destroy()}
-     */
-    @Override
-    public void destroy() {
-        if (mWebView.getViewRootImpl() != null) {
-            Log.e(LOGTAG, Log.getStackTraceString(
-                    new Throwable("Error: WebView.destroy() called while still attached!")));
-        }
-        ensureFunctorDetached();
-        destroyJava();
-        destroyNative();
-    }
-
-    private void ensureFunctorDetached() {
-        if (mWebView.isHardwareAccelerated()) {
-            int drawGLFunction = nativeGetDrawGLFunction(mNativeClass);
-            ViewRootImpl viewRoot = mWebView.getViewRootImpl();
-            if (drawGLFunction != 0 && viewRoot != null) {
-                viewRoot.detachFunctor(drawGLFunction);
-            }
-        }
-    }
-
-    private void destroyJava() {
-        mCallbackProxy.blockMessages();
-        if (mAccessibilityInjector != null) {
-            mAccessibilityInjector.destroy();
-            mAccessibilityInjector = null;
-        }
-        if (mWebViewCore != null) {
-            // Tell WebViewCore to destroy itself
-            synchronized (this) {
-                WebViewCore webViewCore = mWebViewCore;
-                mWebViewCore = null; // prevent using partial webViewCore
-                webViewCore.destroy();
-            }
-            // Remove any pending messages that might not be serviced yet.
-            mPrivateHandler.removeCallbacksAndMessages(null);
-        }
-    }
-
-    private void destroyNative() {
-        if (mNativeClass == 0) return;
-        int nptr = mNativeClass;
-        mNativeClass = 0;
-        if (Thread.currentThread() == mPrivateHandler.getLooper().getThread()) {
-            // We are on the main thread and can safely delete
-            nativeDestroy(nptr);
-        } else {
-            mPrivateHandler.post(new DestroyNativeRunnable(nptr));
-        }
-    }
-
-    private static class DestroyNativeRunnable implements Runnable {
-
-        private int mNativePtr;
-
-        public DestroyNativeRunnable(int nativePtr) {
-            mNativePtr = nativePtr;
-        }
-
-        @Override
-        public void run() {
-            // nativeDestroy also does a stopGL()
-            nativeDestroy(mNativePtr);
-        }
-
-    }
-
-    /**
-     * See {@link WebView#enablePlatformNotifications()}
-     */
-    @Deprecated
-    public static void enablePlatformNotifications() {
-        synchronized (WebViewClassic.class) {
-            sNotificationsEnabled = true;
-            Context context = JniUtil.getContext();
-            if (context != null)
-                setupProxyListener(context);
-        }
-    }
-
-    /**
-     * See {@link WebView#disablePlatformNotifications()}
-     */
-    @Deprecated
-    public static void disablePlatformNotifications() {
-        synchronized (WebViewClassic.class) {
-            sNotificationsEnabled = false;
-            Context context = JniUtil.getContext();
-            if (context != null)
-                disableProxyListener(context);
-        }
-    }
-
-    /**
-     * Sets JavaScript engine flags.
-     *
-     * @param flags JS engine flags in a String
-     *
-     * This is an implementation detail.
-     */
-    public void setJsFlags(String flags) {
-        mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
-    }
-
-    /**
-     * See {@link WebView#setNetworkAvailable(boolean)}
-     */
-    @Override
-    public void setNetworkAvailable(boolean networkUp) {
-        mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
-                networkUp ? 1 : 0, 0);
-    }
-
-    /**
-     * Inform WebView about the current network type.
-     */
-    public void setNetworkType(String type, String subtype) {
-        Map<String, String> map = new HashMap<String, String>();
-        map.put("type", type);
-        map.put("subtype", subtype);
-        mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
-    }
-
-    /**
-     * See {@link WebView#saveState(Bundle)}
-     */
-    @Override
-    public WebBackForwardList saveState(Bundle outState) {
-        if (outState == null) {
-            return null;
-        }
-        // We grab a copy of the back/forward list because a client of WebView
-        // may have invalidated the history list by calling clearHistory.
-        WebBackForwardListClassic list = copyBackForwardList();
-        final int currentIndex = list.getCurrentIndex();
-        final int size = list.getSize();
-        // We should fail saving the state if the list is empty or the index is
-        // not in a valid range.
-        if (currentIndex < 0 || currentIndex >= size || size == 0) {
-            return null;
-        }
-        outState.putInt("index", currentIndex);
-        // FIXME: This should just be a byte[][] instead of ArrayList but
-        // Parcel.java does not have the code to handle multi-dimensional
-        // arrays.
-        ArrayList<byte[]> history = new ArrayList<byte[]>(size);
-        for (int i = 0; i < size; i++) {
-            WebHistoryItemClassic item = list.getItemAtIndex(i);
-            if (null == item) {
-                // FIXME: this shouldn't happen
-                // need to determine how item got set to null
-                Log.w(LOGTAG, "saveState: Unexpected null history item.");
-                return null;
-            }
-            byte[] data = item.getFlattenedData();
-            if (data == null) {
-                // It would be very odd to not have any data for a given history
-                // item. And we will fail to rebuild the history list without
-                // flattened data.
-                return null;
-            }
-            history.add(data);
-        }
-        outState.putSerializable("history", history);
-        if (mCertificate != null) {
-            outState.putBundle("certificate",
-                               SslCertificate.saveState(mCertificate));
-        }
-        outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
-        mZoomManager.saveZoomState(outState);
-        return list;
-    }
-
-    /**
-     * See {@link WebView#savePicture(Bundle, File)}
-     */
-    @Override
-    @Deprecated
-    public boolean savePicture(Bundle b, final File dest) {
-        if (dest == null || b == null) {
-            return false;
-        }
-        final Picture p = capturePicture();
-        // Use a temporary file while writing to ensure the destination file
-        // contains valid data.
-        final File temp = new File(dest.getPath() + ".writing");
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                FileOutputStream out = null;
-                try {
-                    out = new FileOutputStream(temp);
-                    p.writeToStream(out);
-                    // Writing the picture succeeded, rename the temporary file
-                    // to the destination.
-                    temp.renameTo(dest);
-                } catch (Exception e) {
-                    // too late to do anything about it.
-                } finally {
-                    if (out != null) {
-                        try {
-                            out.close();
-                        } catch (Exception e) {
-                            // Can't do anything about that
-                        }
-                    }
-                    temp.delete();
-                }
-            }
-        }).start();
-        // now update the bundle
-        b.putInt("scrollX", getScrollX());
-        b.putInt("scrollY", getScrollY());
-        mZoomManager.saveZoomState(b);
-        return true;
-    }
-
-    private void restoreHistoryPictureFields(Picture p, Bundle b) {
-        int sx = b.getInt("scrollX", 0);
-        int sy = b.getInt("scrollY", 0);
-
-        mDrawHistory = true;
-        mHistoryPicture = p;
-
-        setScrollXRaw(sx);
-        setScrollYRaw(sy);
-        mZoomManager.restoreZoomState(b);
-        final float scale = mZoomManager.getScale();
-        mHistoryWidth = Math.round(p.getWidth() * scale);
-        mHistoryHeight = Math.round(p.getHeight() * scale);
-
-        invalidate();
-    }
-
-    /**
-     * See {@link WebView#restorePicture(Bundle, File)};
-     */
-    @Override
-    @Deprecated
-    public boolean restorePicture(Bundle b, File src) {
-        if (src == null || b == null) {
-            return false;
-        }
-        if (!src.exists()) {
-            return false;
-        }
-        try {
-            final FileInputStream in = new FileInputStream(src);
-            final Bundle copy = new Bundle(b);
-            new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        final Picture p = Picture.createFromStream(in);
-                        if (p != null) {
-                            // Post a runnable on the main thread to update the
-                            // history picture fields.
-                            mPrivateHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    restoreHistoryPictureFields(p, copy);
-                                }
-                            });
-                        }
-                    } finally {
-                        try {
-                            in.close();
-                        } catch (Exception e) {
-                            // Nothing we can do now.
-                        }
-                    }
-                }
-            }).start();
-        } catch (FileNotFoundException e){
-            e.printStackTrace();
-        }
-        return true;
-    }
-
-    /**
-     * Saves the view data to the output stream. The output is highly
-     * version specific, and may not be able to be loaded by newer versions
-     * of WebView.
-     * @param stream The {@link OutputStream} to save to
-     * @param callback The {@link ValueCallback} to call with the result
-     */
-    public void saveViewState(OutputStream stream, ValueCallback<Boolean> callback) {
-        if (mWebViewCore == null) {
-            callback.onReceiveValue(false);
-            return;
-        }
-        mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SAVE_VIEW_STATE,
-                new WebViewCore.SaveViewStateRequest(stream, callback));
-    }
-
-    /**
-     * Loads the view data from the input stream. See
-     * {@link #saveViewState(java.io.OutputStream, ValueCallback)} for more information.
-     * @param stream The {@link InputStream} to load from
-     */
-    public void loadViewState(InputStream stream) {
-        mBlockWebkitViewMessages = true;
-        new AsyncTask<InputStream, Void, DrawData>() {
-
-            @Override
-            protected DrawData doInBackground(InputStream... params) {
-                try {
-                    return ViewStateSerializer.deserializeViewState(params[0]);
-                } catch (IOException e) {
-                    return null;
-                }
-            }
-
-            @Override
-            protected void onPostExecute(DrawData draw) {
-                if (draw == null) {
-                    Log.e(LOGTAG, "Failed to load view state!");
-                    return;
-                }
-                int viewWidth = getViewWidth();
-                int viewHeight = getViewHeightWithTitle() - getTitleHeight();
-                draw.mViewSize = new Point(viewWidth, viewHeight);
-                draw.mViewState.mDefaultScale = getDefaultZoomScale();
-                mLoadedPicture = draw;
-                setNewPicture(mLoadedPicture, true);
-                mLoadedPicture.mViewState = null;
-            }
-
-        }.execute(stream);
-    }
-
-    /**
-     * Clears the view state set with {@link #loadViewState(InputStream)}.
-     * This WebView will then switch to showing the content from webkit
-     */
-    public void clearViewState() {
-        mBlockWebkitViewMessages = false;
-        mLoadedPicture = null;
-        invalidate();
-    }
-
-    /**
-     * See {@link WebView#restoreState(Bundle)}
-     */
-    @Override
-    public WebBackForwardList restoreState(Bundle inState) {
-        WebBackForwardListClassic returnList = null;
-        if (inState == null) {
-            return returnList;
-        }
-        if (inState.containsKey("index") && inState.containsKey("history")) {
-            mCertificate = SslCertificate.restoreState(
-                inState.getBundle("certificate"));
-
-            final WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
-            final int index = inState.getInt("index");
-            // We can't use a clone of the list because we need to modify the
-            // shared copy, so synchronize instead to prevent concurrent
-            // modifications.
-            synchronized (list) {
-                final List<byte[]> history =
-                        (List<byte[]>) inState.getSerializable("history");
-                final int size = history.size();
-                // Check the index bounds so we don't crash in native code while
-                // restoring the history index.
-                if (index < 0 || index >= size) {
-                    return null;
-                }
-                for (int i = 0; i < size; i++) {
-                    byte[] data = history.remove(0);
-                    if (data == null) {
-                        // If we somehow have null data, we cannot reconstruct
-                        // the item and thus our history list cannot be rebuilt.
-                        return null;
-                    }
-                    WebHistoryItem item = new WebHistoryItemClassic(data);
-                    list.addHistoryItem(item);
-                }
-                // Grab the most recent copy to return to the caller.
-                returnList = copyBackForwardList();
-                // Update the copy to have the correct index.
-                returnList.setCurrentIndex(index);
-            }
-            // Restore private browsing setting.
-            if (inState.getBoolean("privateBrowsingEnabled")) {
-                getSettings().setPrivateBrowsingEnabled(true);
-            }
-            mZoomManager.restoreZoomState(inState);
-            // Remove all pending messages because we are restoring previous
-            // state.
-            mWebViewCore.removeMessages();
-            if (isAccessibilityInjectionEnabled()) {
-                getAccessibilityInjector().addAccessibilityApisIfNecessary();
-            }
-            // Send a restore state message.
-            mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
-        }
-        return returnList;
-    }
-
-    /**
-     * See {@link WebView#loadUrl(String, Map)}
-     */
-    @Override
-    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
-        loadUrlImpl(url, additionalHttpHeaders);
-    }
-
-    private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
-        switchOutDrawHistory();
-        WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
-        arg.mUrl = url;
-        arg.mExtraHeaders = extraHeaders;
-        mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
-        clearHelpers();
-    }
-
-    /**
-     * See {@link WebView#loadUrl(String)}
-     */
-    @Override
-    public void loadUrl(String url) {
-        loadUrlImpl(url);
-    }
-
-    private void loadUrlImpl(String url) {
-        if (url == null) {
-            return;
-        }
-        loadUrlImpl(url, null);
-    }
-
-    /**
-     * See {@link WebView#postUrl(String, byte[])}
-     */
-    @Override
-    public void postUrl(String url, byte[] postData) {
-        if (URLUtil.isNetworkUrl(url)) {
-            switchOutDrawHistory();
-            WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
-            arg.mUrl = url;
-            arg.mPostData = postData;
-            mWebViewCore.sendMessage(EventHub.POST_URL, arg);
-            clearHelpers();
-        } else {
-            loadUrlImpl(url);
-        }
-    }
-
-    /**
-     * See {@link WebView#loadData(String, String, String)}
-     */
-    @Override
-    public void loadData(String data, String mimeType, String encoding) {
-        loadDataImpl(data, mimeType, encoding);
-    }
-
-    private void loadDataImpl(String data, String mimeType, String encoding) {
-        StringBuilder dataUrl = new StringBuilder("data:");
-        dataUrl.append(mimeType);
-        if ("base64".equals(encoding)) {
-            dataUrl.append(";base64");
-        }
-        dataUrl.append(",");
-        dataUrl.append(data);
-        loadUrlImpl(dataUrl.toString());
-    }
-
-    /**
-     * See {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}
-     */
-    @Override
-    public void loadDataWithBaseURL(String baseUrl, String data,
-            String mimeType, String encoding, String historyUrl) {
-
-        if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
-            loadDataImpl(data, mimeType, encoding);
-            return;
-        }
-        switchOutDrawHistory();
-        WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
-        arg.mBaseUrl = baseUrl;
-        arg.mData = data;
-        arg.mMimeType = mimeType;
-        arg.mEncoding = encoding;
-        arg.mHistoryUrl = historyUrl;
-        mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
-        clearHelpers();
-    }
-
-    @Override
-    public void evaluateJavaScript(String script, ValueCallback<String> resultCallback) {
-        // K-only API not implemented in WebViewClassic.
-        throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
-    }
-
-    /**
-     * See {@link WebView#saveWebArchive(String)}
-     */
-    @Override
-    public void saveWebArchive(String filename) {
-        saveWebArchiveImpl(filename, false, null);
-    }
-
-    /* package */ static class SaveWebArchiveMessage {
-        SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
-            mBasename = basename;
-            mAutoname = autoname;
-            mCallback = callback;
-        }
-
-        /* package */ final String mBasename;
-        /* package */ final boolean mAutoname;
-        /* package */ final ValueCallback<String> mCallback;
-        /* package */ String mResultFile;
-    }
-
-    /**
-     * See {@link WebView#saveWebArchive(String, boolean, ValueCallback)}
-     */
-    @Override
-    public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
-        saveWebArchiveImpl(basename, autoname, callback);
-    }
-
-    private void saveWebArchiveImpl(String basename, boolean autoname,
-            ValueCallback<String> callback) {
-        mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
-            new SaveWebArchiveMessage(basename, autoname, callback));
-    }
-
-    /**
-     * See {@link WebView#stopLoading()}
-     */
-    @Override
-    public void stopLoading() {
-        // TODO: should we clear all the messages in the queue before sending
-        // STOP_LOADING?
-        switchOutDrawHistory();
-        mWebViewCore.sendMessage(EventHub.STOP_LOADING);
-    }
-
-    /**
-     * See {@link WebView#reload()}
-     */
-    @Override
-    public void reload() {
-        clearHelpers();
-        switchOutDrawHistory();
-        mWebViewCore.sendMessage(EventHub.RELOAD);
-    }
-
-    /**
-     * See {@link WebView#canGoBack()}
-     */
-    @Override
-    public boolean canGoBack() {
-        WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
-        synchronized (l) {
-            if (l.getClearPending()) {
-                return false;
-            } else {
-                return l.getCurrentIndex() > 0;
-            }
-        }
-    }
-
-    /**
-     * See {@link WebView#goBack()}
-     */
-    @Override
-    public void goBack() {
-        goBackOrForwardImpl(-1);
-    }
-
-    /**
-     * See {@link WebView#canGoForward()}
-     */
-    @Override
-    public boolean canGoForward() {
-        WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
-        synchronized (l) {
-            if (l.getClearPending()) {
-                return false;
-            } else {
-                return l.getCurrentIndex() < l.getSize() - 1;
-            }
-        }
-    }
-
-    /**
-     * See {@link WebView#goForward()}
-     */
-    @Override
-    public void goForward() {
-        goBackOrForwardImpl(1);
-    }
-
-    /**
-     * See {@link WebView#canGoBackOrForward(int)}
-     */
-    @Override
-    public boolean canGoBackOrForward(int steps) {
-        WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
-        synchronized (l) {
-            if (l.getClearPending()) {
-                return false;
-            } else {
-                int newIndex = l.getCurrentIndex() + steps;
-                return newIndex >= 0 && newIndex < l.getSize();
-            }
-        }
-    }
-
-    /**
-     * See {@link WebView#goBackOrForward(int)}
-     */
-    @Override
-    public void goBackOrForward(int steps) {
-        goBackOrForwardImpl(steps);
-    }
-
-    private void goBackOrForwardImpl(int steps) {
-        goBackOrForward(steps, false);
-    }
-
-    private void goBackOrForward(int steps, boolean ignoreSnapshot) {
-        if (steps != 0) {
-            clearHelpers();
-            mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
-                    ignoreSnapshot ? 1 : 0);
-        }
-    }
-
-    /**
-     * See {@link WebView#isPrivateBrowsingEnabled()}
-     */
-    @Override
-    public boolean isPrivateBrowsingEnabled() {
-        WebSettingsClassic settings = getSettings();
-        return (settings != null) ? settings.isPrivateBrowsingEnabled() : false;
-    }
-
-    private void startPrivateBrowsing() {
-        getSettings().setPrivateBrowsingEnabled(true);
-    }
-
-    private boolean extendScroll(int y) {
-        int finalY = mScroller.getFinalY();
-        int newY = pinLocY(finalY + y);
-        if (newY == finalY) return false;
-        mScroller.setFinalY(newY);
-        mScroller.extendDuration(computeDuration(0, y));
-        return true;
-    }
-
-    /**
-     * See {@link WebView#pageUp(boolean)}
-     */
-    @Override
-    public boolean pageUp(boolean top) {
-        if (mNativeClass == 0) {
-            return false;
-        }
-        if (top) {
-            // go to the top of the document
-            return pinScrollTo(getScrollX(), 0, true, 0);
-        }
-        // Page up
-        int h = getHeight();
-        int y;
-        if (h > 2 * PAGE_SCROLL_OVERLAP) {
-            y = -h + PAGE_SCROLL_OVERLAP;
-        } else {
-            y = -h / 2;
-        }
-        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
-                : extendScroll(y);
-    }
-
-    /**
-     * See {@link WebView#pageDown(boolean)}
-     */
-    @Override
-    public boolean pageDown(boolean bottom) {
-        if (mNativeClass == 0) {
-            return false;
-        }
-        if (bottom) {
-            return pinScrollTo(getScrollX(), computeRealVerticalScrollRange(), true, 0);
-        }
-        // Page down.
-        int h = getHeight();
-        int y;
-        if (h > 2 * PAGE_SCROLL_OVERLAP) {
-            y = h - PAGE_SCROLL_OVERLAP;
-        } else {
-            y = h / 2;
-        }
-        return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
-                : extendScroll(y);
-    }
-
-    /**
-     * See {@link WebView#clearView()}
-     */
-    @Override
-    public void clearView() {
-        mContentWidth = 0;
-        mContentHeight = 0;
-        setBaseLayer(0, false, false);
-        mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
-    }
-
-    /**
-     * See {@link WebView#capturePicture()}
-     */
-    @Override
-    public Picture capturePicture() {
-        if (mNativeClass == 0) return null;
-        Picture result = new Picture();
-        nativeCopyBaseContentToPicture(result);
-        return result;
-    }
-
-    /**
-     * See {@link WebView#createPrintDocumentAdapter()}
-     */
-    @Override
-    public PrintDocumentAdapter createPrintDocumentAdapter() {
-        // K-only API not implemented in WebViewClassic.
-        throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
-
-    }
-
-    /**
-     * See {@link WebView#getScale()}
-     */
-    @Override
-    public float getScale() {
-        return mZoomManager.getScale();
-    }
-
-    /**
-     * Compute the reading level scale of the WebView
-     * @param scale The current scale.
-     * @return The reading level scale.
-     */
-    /*package*/ float computeReadingLevelScale(float scale) {
-        return mZoomManager.computeReadingLevelScale(scale);
-    }
-
-    /**
-     * See {@link WebView#setInitialScale(int)}
-     */
-    @Override
-    public void setInitialScale(int scaleInPercent) {
-        mZoomManager.setInitialScaleInPercent(scaleInPercent);
-    }
-
-    /**
-     * See {@link WebView#invokeZoomPicker()}
-     */
-    @Override
-    public void invokeZoomPicker() {
-        if (!getSettings().supportZoom()) {
-            Log.w(LOGTAG, "This WebView doesn't support zoom.");
-            return;
-        }
-        clearHelpers();
-        mZoomManager.invokeZoomPicker();
-    }
-
-    /**
-     * See {@link WebView#getHitTestResult()}
-     */
-    @Override
-    public HitTestResult getHitTestResult() {
-        return mInitialHitTestResult;
-    }
-
-    // No left edge for double-tap zoom alignment
-    static final int NO_LEFTEDGE = -1;
-
-    int getBlockLeftEdge(int x, int y, float readingScale) {
-        float invReadingScale = 1.0f / readingScale;
-        int readingWidth = (int) (getViewWidth() * invReadingScale);
-        int left = NO_LEFTEDGE;
-        if (mFocusedNode != null) {
-            final int length = mFocusedNode.mEnclosingParentRects.length;
-            for (int i = 0; i < length; i++) {
-                Rect rect = mFocusedNode.mEnclosingParentRects[i];
-                if (rect.width() < mFocusedNode.mHitTestSlop) {
-                    // ignore bounding boxes that are too small
-                    continue;
-                } else if (rect.width() > readingWidth) {
-                    // stop when bounding box doesn't fit the screen width
-                    // at reading scale
-                    break;
-                }
-
-                left = rect.left;
-            }
-        }
-
-        return left;
-    }
-
-    /**
-     * See {@link WebView#requestFocusNodeHref(Message)}
-     */
-    @Override
-    public void requestFocusNodeHref(Message hrefMsg) {
-        if (hrefMsg == null) {
-            return;
-        }
-        int contentX = viewToContentX(mLastTouchX + getScrollX());
-        int contentY = viewToContentY(mLastTouchY + getScrollY());
-        if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX
-                && mFocusedNode.mHitTestY == contentY) {
-            hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl);
-            hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText);
-            hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl);
-            hrefMsg.sendToTarget();
-            return;
-        }
-        mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
-                contentX, contentY, hrefMsg);
-    }
-
-    /**
-     * See {@link WebView#requestImageRef(Message)}
-     */
-    @Override
-    public void requestImageRef(Message msg) {
-        if (0 == mNativeClass) return; // client isn't initialized
-        String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null;
-        Bundle data = msg.getData();
-        data.putString("url", url);
-        msg.setData(data);
-        msg.sendToTarget();
-    }
-
-    static int pinLoc(int x, int viewMax, int docMax) {
-//        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
-        if (docMax < viewMax) {   // the doc has room on the sides for "blank"
-            // pin the short document to the top/left of the screen
-            x = 0;
-//            Log.d(LOGTAG, "--- center " + x);
-        } else if (x < 0) {
-            x = 0;
-//            Log.d(LOGTAG, "--- zero");
-        } else if (x + viewMax > docMax) {
-            x = docMax - viewMax;
-//            Log.d(LOGTAG, "--- pin " + x);
-        }
-        return x;
-    }
-
-    // Expects x in view coordinates
-    int pinLocX(int x) {
-        if (mInOverScrollMode) return x;
-        return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
-    }
-
-    // Expects y in view coordinates
-    int pinLocY(int y) {
-        if (mInOverScrollMode) return y;
-        return pinLoc(y, getViewHeightWithTitle(),
-                      computeRealVerticalScrollRange() + getTitleHeight());
-    }
-
-    /**
-     * Given a distance in view space, convert it to content space. Note: this
-     * does not reflect translation, just scaling, so this should not be called
-     * with coordinates, but should be called for dimensions like width or
-     * height.
-     */
-    private int viewToContentDimension(int d) {
-        return Math.round(d * mZoomManager.getInvScale());
-    }
-
-    /**
-     * Given an x coordinate in view space, convert it to content space.  Also
-     * may be used for absolute heights.
-     */
-    /*package*/ int viewToContentX(int x) {
-        return viewToContentDimension(x);
-    }
-
-    /**
-     * Given a y coordinate in view space, convert it to content space.
-     * Takes into account the height of the title bar if there is one
-     * embedded into the WebView.
-     */
-    /*package*/ int viewToContentY(int y) {
-        return viewToContentDimension(y - getTitleHeight());
-    }
-
-    /**
-     * Given a x coordinate in view space, convert it to content space.
-     * Returns the result as a float.
-     */
-    private float viewToContentXf(int x) {
-        return x * mZoomManager.getInvScale();
-    }
-
-    /**
-     * Given a y coordinate in view space, convert it to content space.
-     * Takes into account the height of the title bar if there is one
-     * embedded into the WebView. Returns the result as a float.
-     */
-    private float viewToContentYf(int y) {
-        return (y - getTitleHeight()) * mZoomManager.getInvScale();
-    }
-
-    /**
-     * Given a distance in content space, convert it to view space. Note: this
-     * does not reflect translation, just scaling, so this should not be called
-     * with coordinates, but should be called for dimensions like width or
-     * height.
-     */
-    /*package*/ int contentToViewDimension(int d) {
-        return Math.round(d * mZoomManager.getScale());
-    }
-
-    /**
-     * Given an x coordinate in content space, convert it to view
-     * space.
-     */
-    /*package*/ int contentToViewX(int x) {
-        return contentToViewDimension(x);
-    }
-
-    /**
-     * Given a y coordinate in content space, convert it to view
-     * space.  Takes into account the height of the title bar.
-     */
-    /*package*/ int contentToViewY(int y) {
-        return contentToViewDimension(y) + getTitleHeight();
-    }
-
-    private Rect contentToViewRect(Rect x) {
-        return new Rect(contentToViewX(x.left), contentToViewY(x.top),
-                        contentToViewX(x.right), contentToViewY(x.bottom));
-    }
-
-    /*  To invalidate a rectangle in content coordinates, we need to transform
-        the rect into view coordinates, so we can then call invalidate(...).
-
-        Normally, we would just call contentToView[XY](...), which eventually
-        calls Math.round(coordinate * mActualScale). However, for invalidates,
-        we need to account for the slop that occurs with antialiasing. To
-        address that, we are a little more liberal in the size of the rect that
-        we invalidate.
-
-        This liberal calculation calls floor() for the top/left, and ceil() for
-        the bottom/right coordinates. This catches the possible extra pixels of
-        antialiasing that we might have missed with just round().
-     */
-
-    // Called by JNI to invalidate the View, given rectangle coordinates in
-    // content space
-    private void viewInvalidate(int l, int t, int r, int b) {
-        final float scale = mZoomManager.getScale();
-        final int dy = getTitleHeight();
-        mWebView.invalidate((int)Math.floor(l * scale),
-                (int)Math.floor(t * scale) + dy,
-                (int)Math.ceil(r * scale),
-                (int)Math.ceil(b * scale) + dy);
-    }
-
-    // Called by JNI to invalidate the View after a delay, given rectangle
-    // coordinates in content space
-    private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
-        final float scale = mZoomManager.getScale();
-        final int dy = getTitleHeight();
-        mWebView.postInvalidateDelayed(delay,
-                (int)Math.floor(l * scale),
-                (int)Math.floor(t * scale) + dy,
-                (int)Math.ceil(r * scale),
-                (int)Math.ceil(b * scale) + dy);
-    }
-
-    private void invalidateContentRect(Rect r) {
-        viewInvalidate(r.left, r.top, r.right, r.bottom);
-    }
-
-    // stop the scroll animation, and don't let a subsequent fling add
-    // to the existing velocity
-    private void abortAnimation() {
-        mScroller.abortAnimation();
-        mLastVelocity = 0;
-    }
-
-    /* call from webcoreview.draw(), so we're still executing in the UI thread
-    */
-    private void recordNewContentSize(int w, int h, boolean updateLayout) {
-
-        // premature data from webkit, ignore
-        if ((w | h) == 0) {
-            invalidate();
-            return;
-        }
-
-        // don't abort a scroll animation if we didn't change anything
-        if (mContentWidth != w || mContentHeight != h) {
-            // record new dimensions
-            mContentWidth = w;
-            mContentHeight = h;
-            // If history Picture is drawn, don't update scroll. They will be
-            // updated when we get out of that mode.
-            if (!mDrawHistory) {
-                // repin our scroll, taking into account the new content size
-                updateScrollCoordinates(pinLocX(getScrollX()), pinLocY(getScrollY()));
-                if (!mScroller.isFinished()) {
-                    // We are in the middle of a scroll.  Repin the final scroll
-                    // position.
-                    mScroller.setFinalX(pinLocX(mScroller.getFinalX()));
-                    mScroller.setFinalY(pinLocY(mScroller.getFinalY()));
-                }
-            }
-            invalidate();
-        }
-        contentSizeChanged(updateLayout);
-    }
-
-    // Used to avoid sending many visible rect messages.
-    private Rect mLastVisibleRectSent = new Rect();
-    private Rect mLastGlobalRect = new Rect();
-    private Rect mVisibleRect = new Rect();
-    private Rect mGlobalVisibleRect = new Rect();
-    private Point mScrollOffset = new Point();
-
-    Rect sendOurVisibleRect() {
-        if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
-        calcOurContentVisibleRect(mVisibleRect);
-        // Rect.equals() checks for null input.
-        if (!mVisibleRect.equals(mLastVisibleRectSent)) {
-            if (!mBlockWebkitViewMessages) {
-                mScrollOffset.set(mVisibleRect.left, mVisibleRect.top);
-                mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
-                mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
-                        mSendScrollEvent ? 1 : 0, mScrollOffset);
-            }
-            mLastVisibleRectSent.set(mVisibleRect);
-            mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-        }
-        if (mWebView.getGlobalVisibleRect(mGlobalVisibleRect)
-                && !mGlobalVisibleRect.equals(mLastGlobalRect)) {
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + ","
-                        + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b="
-                        + mGlobalVisibleRect.bottom);
-            }
-            // TODO: the global offset is only used by windowRect()
-            // in ChromeClientAndroid ; other clients such as touch
-            // and mouse events could return view + screen relative points.
-            if (!mBlockWebkitViewMessages) {
-                mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect);
-            }
-            mLastGlobalRect.set(mGlobalVisibleRect);
-        }
-        return mVisibleRect;
-    }
-
-    private Point mGlobalVisibleOffset = new Point();
-    // Sets r to be the visible rectangle of our webview in view coordinates
-    private void calcOurVisibleRect(Rect r) {
-        mWebView.getGlobalVisibleRect(r, mGlobalVisibleOffset);
-        r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y);
-    }
-
-    // Sets r to be our visible rectangle in content coordinates
-    private void calcOurContentVisibleRect(Rect r) {
-        calcOurVisibleRect(r);
-        r.left = viewToContentX(r.left);
-        // viewToContentY will remove the total height of the title bar.  Add
-        // the visible height back in to account for the fact that if the title
-        // bar is partially visible, the part of the visible rect which is
-        // displaying our content is displaced by that amount.
-        r.top = viewToContentY(r.top + getVisibleTitleHeightImpl());
-        r.right = viewToContentX(r.right);
-        r.bottom = viewToContentY(r.bottom);
-    }
-
-    private final Rect mTempContentVisibleRect = new Rect();
-    // Sets r to be our visible rectangle in content coordinates. We use this
-    // method on the native side to compute the position of the fixed layers.
-    // Uses floating coordinates (necessary to correctly place elements when
-    // the scale factor is not 1)
-    private void calcOurContentVisibleRectF(RectF r) {
-        calcOurVisibleRect(mTempContentVisibleRect);
-        viewToContentVisibleRect(r, mTempContentVisibleRect);
-    }
-
-    static class ViewSizeData {
-        int mWidth;
-        int mHeight;
-        float mHeightWidthRatio;
-        int mActualViewHeight;
-        int mTextWrapWidth;
-        int mAnchorX;
-        int mAnchorY;
-        float mScale;
-        boolean mIgnoreHeight;
-    }
-
-    /**
-     * Compute unzoomed width and height, and if they differ from the last
-     * values we sent, send them to webkit (to be used as new viewport)
-     *
-     * @param force ensures that the message is sent to webkit even if the width
-     * or height has not changed since the last message
-     *
-     * @return true if new values were sent
-     */
-    boolean sendViewSizeZoom(boolean force) {
-        if (mBlockWebkitViewMessages) return false;
-        if (mZoomManager.isPreventingWebkitUpdates()) return false;
-
-        int viewWidth = getViewWidth();
-        int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
-        // This height could be fixed and be different from actual visible height.
-        int viewHeight = getViewHeightWithTitle() - getTitleHeight();
-        int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
-        // Make the ratio more accurate than (newHeight / newWidth), since the
-        // latter both are calculated and rounded.
-        float heightWidthRatio = (float) viewHeight / viewWidth;
-        /*
-         * Because the native side may have already done a layout before the
-         * View system was able to measure us, we have to send a height of 0 to
-         * remove excess whitespace when we grow our width. This will trigger a
-         * layout and a change in content size. This content size change will
-         * mean that contentSizeChanged will either call this method directly or
-         * indirectly from onSizeChanged.
-         */
-        if (newWidth > mLastWidthSent && mWrapContent) {
-            newHeight = 0;
-            heightWidthRatio = 0;
-        }
-        // Actual visible content height.
-        int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
-        // Avoid sending another message if the dimensions have not changed.
-        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force ||
-                actualViewHeight != mLastActualHeightSent) {
-            ViewSizeData data = new ViewSizeData();
-            data.mWidth = newWidth;
-            data.mHeight = newHeight;
-            data.mHeightWidthRatio = heightWidthRatio;
-            data.mActualViewHeight = actualViewHeight;
-            data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
-            data.mScale = mZoomManager.getScale();
-            data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
-                    && !mHeightCanMeasure;
-            data.mAnchorX = mZoomManager.getDocumentAnchorX();
-            data.mAnchorY = mZoomManager.getDocumentAnchorY();
-            mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
-            mLastWidthSent = newWidth;
-            mLastHeightSent = newHeight;
-            mLastActualHeightSent = actualViewHeight;
-            mZoomManager.clearDocumentAnchor();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Update the double-tap zoom.
-     */
-    /* package */ void updateDoubleTapZoom(int doubleTapZoom) {
-        mZoomManager.updateDoubleTapZoom(doubleTapZoom);
-    }
-
-    private int computeRealHorizontalScrollRange() {
-        if (mDrawHistory) {
-            return mHistoryWidth;
-        } else {
-            // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentWidth * mZoomManager.getScale());
-        }
-    }
-
-    @Override
-    public int computeHorizontalScrollRange() {
-        int range = computeRealHorizontalScrollRange();
-
-        // Adjust reported range if overscrolled to compress the scroll bars
-        final int scrollX = getScrollX();
-        final int overscrollRight = computeMaxScrollX();
-        if (scrollX < 0) {
-            range -= scrollX;
-        } else if (scrollX > overscrollRight) {
-            range += scrollX - overscrollRight;
-        }
-
-        return range;
-    }
-
-    @Override
-    public int computeHorizontalScrollOffset() {
-        return Math.max(getScrollX(), 0);
-    }
-
-    private int computeRealVerticalScrollRange() {
-        if (mDrawHistory) {
-            return mHistoryHeight;
-        } else {
-            // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentHeight * mZoomManager.getScale());
-        }
-    }
-
-    @Override
-    public int computeVerticalScrollRange() {
-        int range = computeRealVerticalScrollRange();
-
-        // Adjust reported range if overscrolled to compress the scroll bars
-        final int scrollY = getScrollY();
-        final int overscrollBottom = computeMaxScrollY();
-        if (scrollY < 0) {
-            range -= scrollY;
-        } else if (scrollY > overscrollBottom) {
-            range += scrollY - overscrollBottom;
-        }
-
-        return range;
-    }
-
-    @Override
-    public int computeVerticalScrollOffset() {
-        return Math.max(getScrollY() - getTitleHeight(), 0);
-    }
-
-    @Override
-    public int computeVerticalScrollExtent() {
-        return getViewHeight();
-    }
-
-    @Override
-    public void onDrawVerticalScrollBar(Canvas canvas,
-                                           Drawable scrollBar,
-                                           int l, int t, int r, int b) {
-        if (getScrollY() < 0) {
-            t -= getScrollY();
-        }
-        scrollBar.setBounds(l, t + getVisibleTitleHeightImpl(), r, b);
-        scrollBar.draw(canvas);
-    }
-
-    @Override
-    public void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
-            boolean clampedY) {
-        // Special-case layer scrolling so that we do not trigger normal scroll
-        // updating.
-        if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
-            scrollEditText(scrollX, scrollY);
-            return;
-        }
-        if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
-            scrollLayerTo(scrollX, scrollY);
-            animateHandles();
-            return;
-        }
-        mInOverScrollMode = false;
-        int maxX = computeMaxScrollX();
-        int maxY = computeMaxScrollY();
-        if (maxX == 0) {
-            // do not over scroll x if the page just fits the screen
-            scrollX = pinLocX(scrollX);
-        } else if (scrollX < 0 || scrollX > maxX) {
-            mInOverScrollMode = true;
-        }
-        if (scrollY < 0 || scrollY > maxY) {
-            mInOverScrollMode = true;
-        }
-
-        int oldX = getScrollX();
-        int oldY = getScrollY();
-
-        mWebViewPrivate.super_scrollTo(scrollX, scrollY);
-
-        animateHandles();
-
-        if (mOverScrollGlow != null) {
-            mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY);
-        }
-    }
-
-    /**
-     * See {@link WebView#getUrl()}
-     */
-    @Override
-    public String getUrl() {
-        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
-        return h != null ? h.getUrl() : null;
-    }
-
-    /**
-     * See {@link WebView#getOriginalUrl()}
-     */
-    @Override
-    public String getOriginalUrl() {
-        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
-        return h != null ? h.getOriginalUrl() : null;
-    }
-
-    /**
-     * See {@link WebView#getTitle()}
-     */
-    @Override
-    public String getTitle() {
-        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
-        return h != null ? h.getTitle() : null;
-    }
-
-    /**
-     * See {@link WebView#getFavicon()}
-     */
-    @Override
-    public Bitmap getFavicon() {
-        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
-        return h != null ? h.getFavicon() : null;
-    }
-
-    /**
-     * See {@link WebView#getTouchIconUrl()}
-     */
-    @Override
-    public String getTouchIconUrl() {
-        WebHistoryItemClassic h = mCallbackProxy.getBackForwardList().getCurrentItem();
-        return h != null ? h.getTouchIconUrl() : null;
-    }
-
-    /**
-     * See {@link WebView#getProgress()}
-     */
-    @Override
-    public int getProgress() {
-        return mCallbackProxy.getProgress();
-    }
-
-    /**
-     * See {@link WebView#getContentHeight()}
-     */
-    @Override
-    public int getContentHeight() {
-        return mContentHeight;
-    }
-
-    /**
-     * See {@link WebView#getContentWidth()}
-     */
-    @Override
-    public int getContentWidth() {
-        return mContentWidth;
-    }
-
-    public int getPageBackgroundColor() {
-        if (mNativeClass == 0) return Color.WHITE;
-        return nativeGetBackgroundColor(mNativeClass);
-    }
-
-    /**
-     * See {@link WebView#pauseTimers()}
-     */
-    @Override
-    public void pauseTimers() {
-        mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
-    }
-
-    /**
-     * See {@link WebView#resumeTimers()}
-     */
-    @Override
-    public void resumeTimers() {
-        mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
-    }
-
-    /**
-     * See {@link WebView#onPause()}
-     */
-    @Override
-    public void onPause() {
-        if (!mIsPaused) {
-            mIsPaused = true;
-            mWebViewCore.sendMessage(EventHub.ON_PAUSE);
-            // We want to pause the current playing video when switching out
-            // from the current WebView/tab.
-            if (mHTML5VideoViewProxy != null) {
-                mHTML5VideoViewProxy.pauseAndDispatch();
-            }
-            if (mNativeClass != 0) {
-                nativeSetPauseDrawing(mNativeClass, true);
-            }
-
-            cancelDialogs();
-            WebCoreThreadWatchdog.pause();
-        }
-    }
-
-    @Override
-    public void onWindowVisibilityChanged(int visibility) {
-        updateDrawingState();
-    }
-
-    void updateDrawingState() {
-        if (mNativeClass == 0 || mIsPaused) return;
-        if (mWebView.getWindowVisibility() != View.VISIBLE) {
-            nativeSetPauseDrawing(mNativeClass, true);
-        } else if (mWebView.getVisibility() != View.VISIBLE) {
-            nativeSetPauseDrawing(mNativeClass, true);
-        } else {
-            nativeSetPauseDrawing(mNativeClass, false);
-        }
-    }
-
-    /**
-     * See {@link WebView#onResume()}
-     */
-    @Override
-    public void onResume() {
-        if (mIsPaused) {
-            mIsPaused = false;
-            mWebViewCore.sendMessage(EventHub.ON_RESUME);
-            if (mNativeClass != 0) {
-                nativeSetPauseDrawing(mNativeClass, false);
-            }
-        }
-        // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
-        // to ensure that the Watchdog thread is running for the new WebView, so call
-        // it outside the if block above.
-        WebCoreThreadWatchdog.resume();
-    }
-
-    /**
-     * See {@link WebView#isPaused()}
-     */
-    @Override
-    public boolean isPaused() {
-        return mIsPaused;
-    }
-
-    /**
-     * See {@link WebView#freeMemory()}
-     */
-    @Override
-    public void freeMemory() {
-        mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
-    }
-
-    /**
-     * See {@link WebView#clearCache(boolean)}
-     */
-    @Override
-    public void clearCache(boolean includeDiskFiles) {
-        // 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.
-        mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
-                includeDiskFiles ? 1 : 0, 0);
-    }
-
-    /**
-     * See {@link WebView#clearFormData()}
-     */
-    @Override
-    public void clearFormData() {
-        if (mAutoCompletePopup != null) {
-            mAutoCompletePopup.clearAdapter();
-        }
-    }
-
-    /**
-     * See {@link WebView#clearHistory()}
-     */
-    @Override
-    public void clearHistory() {
-        mCallbackProxy.getBackForwardList().setClearPending();
-        mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
-    }
-
-    /**
-     * See {@link WebView#clearSslPreferences()}
-     */
-    @Override
-    public void clearSslPreferences() {
-        mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
-    }
-
-    /**
-     * See {@link WebView#copyBackForwardList()}
-     */
-    @Override
-    public WebBackForwardListClassic copyBackForwardList() {
-        return mCallbackProxy.getBackForwardList().clone();
-    }
-
-    /**
-     * See {@link WebView#setFindListener(WebView.FindListener)}.
-     * @hide
-     */
-     @Override
-    public void setFindListener(WebView.FindListener listener) {
-         mFindListener = listener;
-     }
-
-    /**
-     * See {@link WebView#findNext(boolean)}
-     */
-    @Override
-    public void findNext(boolean forward) {
-        if (0 == mNativeClass) return; // client isn't initialized
-        if (mFindRequest != null) {
-            mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest);
-        }
-    }
-
-    /**
-     * See {@link WebView#findAll(String)}
-     */
-    @Override
-    public int findAll(String find) {
-        return findAllBody(find, false);
-    }
-
-    @Override
-    public void findAllAsync(String find) {
-        findAllBody(find, true);
-    }
-
-    private int findAllBody(String find, boolean isAsync) {
-        if (0 == mNativeClass) return 0; // client isn't initialized
-        mFindRequest = null;
-        if (find == null) return 0;
-        mWebViewCore.removeMessages(EventHub.FIND_ALL);
-        mFindRequest = new WebViewCore.FindAllRequest(find);
-        if (isAsync) {
-            mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest);
-            return 0; // no need to wait for response
-        }
-        synchronized(mFindRequest) {
-            try {
-                mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest);
-                while (mFindRequest.mMatchCount == -1) {
-                    mFindRequest.wait();
-                }
-            }
-            catch (InterruptedException e) {
-                return 0;
-            }
-            return mFindRequest.mMatchCount;
-        }
-    }
-
-    /**
-     * Start an ActionMode for finding text in this WebView.  Only works if this
-     *              WebView is attached to the view system.
-     * @param text If non-null, will be the initial text to search for.
-     *             Otherwise, the last String searched for in this WebView will
-     *             be used to start.
-     * @param showIme If true, show the IME, assuming the user will begin typing.
-     *             If false and text is non-null, perform a find all.
-     * @return boolean True if the find dialog is shown, false otherwise.
-     */
-    @Override
-    public boolean showFindDialog(String text, boolean showIme) {
-        FindActionModeCallback callback = new FindActionModeCallback(mContext);
-        if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
-            // Could not start the action mode, so end Find on page
-            return false;
-        }
-        mCachedOverlappingActionModeHeight = -1;
-        mFindCallback = callback;
-        setFindIsUp(true);
-        mFindCallback.setWebView(getWebView());
-        if (showIme) {
-            mFindCallback.showSoftInput();
-        } else if (text != null) {
-            mFindCallback.setText(text);
-            mFindCallback.findAll();
-            return true;
-        }
-        if (text == null) {
-            text = mFindRequest == null ? null : mFindRequest.mSearchText;
-        }
-        if (text != null) {
-            mFindCallback.setText(text);
-            mFindCallback.findAll();
-        }
-        return true;
-    }
-
-    /**
-     * Keep track of the find callback so that we can remove its titlebar if
-     * necessary.
-     */
-    private FindActionModeCallback mFindCallback;
-
-    /**
-     * Toggle whether the find dialog is showing, for both native and Java.
-     */
-    private void setFindIsUp(boolean isUp) {
-        mFindIsUp = isUp;
-    }
-
-    // Used to know whether the find dialog is open.  Affects whether
-    // or not we draw the highlights for matches.
-    private boolean mFindIsUp;
-
-    // Keep track of the last find request sent.
-    private WebViewCore.FindAllRequest mFindRequest = null;
-
-    /**
-     * Return the first substring consisting of the address of a physical
-     * location. Currently, only addresses in the United States are detected,
-     * and consist of:
-     * - a house number
-     * - a street name
-     * - a street type (Road, Circle, etc), either spelled out or abbreviated
-     * - a city name
-     * - a state or territory, either spelled out or two-letter abbr.
-     * - an optional 5 digit or 9 digit zip code.
-     *
-     * All names must be correctly capitalized, and the zip code, if present,
-     * must be valid for the state. The street type must be a standard USPS
-     * spelling or abbreviation. The state or territory must also be spelled
-     * or abbreviated using USPS standards. The house number may not exceed
-     * five digits.
-     * @param addr The string to search for addresses.
-     *
-     * @return the address, or if no address is found, return null.
-     */
-    public static String findAddress(String addr) {
-        return findAddress(addr, false);
-    }
-
-    /**
-     * Return the first substring consisting of the address of a physical
-     * location. Currently, only addresses in the United States are detected,
-     * and consist of:
-     * - a house number
-     * - a street name
-     * - a street type (Road, Circle, etc), either spelled out or abbreviated
-     * - a city name
-     * - a state or territory, either spelled out or two-letter abbr.
-     * - an optional 5 digit or 9 digit zip code.
-     *
-     * Names are optionally capitalized, and the zip code, if present,
-     * must be valid for the state. The street type must be a standard USPS
-     * spelling or abbreviation. The state or territory must also be spelled
-     * or abbreviated using USPS standards. The house number may not exceed
-     * five digits.
-     * @param addr The string to search for addresses.
-     * @param caseInsensitive addr Set to true to make search ignore case.
-     *
-     * @return the address, or if no address is found, return null.
-     */
-    public static String findAddress(String addr, boolean caseInsensitive) {
-        return WebViewCore.nativeFindAddress(addr, caseInsensitive);
-    }
-
-    /**
-     * See {@link WebView#clearMatches()}
-     */
-    @Override
-    public void clearMatches() {
-        if (mNativeClass == 0)
-            return;
-        mWebViewCore.removeMessages(EventHub.FIND_ALL);
-        mWebViewCore.sendMessage(EventHub.FIND_ALL, null);
-    }
-
-
-    /**
-     * Called when the find ActionMode ends.
-     */
-    @Override
-    public void notifyFindDialogDismissed() {
-        mFindCallback = null;
-        mCachedOverlappingActionModeHeight = -1;
-        if (mWebViewCore == null) {
-            return;
-        }
-        clearMatches();
-        setFindIsUp(false);
-        // Now that the dialog has been removed, ensure that we scroll to a
-        // location that is not beyond the end of the page.
-        pinScrollTo(getScrollX(), getScrollY(), false, 0);
-        invalidate();
-    }
-
-    /**
-     * See {@link WebView#documentHasImages(Message)}
-     */
-    @Override
-    public void documentHasImages(Message response) {
-        if (response == null) {
-            return;
-        }
-        mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
-    }
-
-    /**
-     * Request the scroller to abort any ongoing animation
-     */
-    public void stopScroll() {
-        mScroller.forceFinished(true);
-        mLastVelocity = 0;
-    }
-
-    @Override
-    public void computeScroll() {
-        if (mScroller.computeScrollOffset()) {
-            int oldX = getScrollX();
-            int oldY = getScrollY();
-            int x = mScroller.getCurrX();
-            int y = mScroller.getCurrY();
-            invalidate();  // So we draw again
-
-            if (!mScroller.isFinished()) {
-                int rangeX = computeMaxScrollX();
-                int rangeY = computeMaxScrollY();
-                int overflingDistance = mOverflingDistance;
-
-                // Use the layer's scroll data if needed.
-                if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
-                    oldX = mScrollingLayerRect.left;
-                    oldY = mScrollingLayerRect.top;
-                    rangeX = mScrollingLayerRect.right;
-                    rangeY = mScrollingLayerRect.bottom;
-                    // No overscrolling for layers.
-                    overflingDistance = 0;
-                } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
-                    oldX = getTextScrollX();
-                    oldY = getTextScrollY();
-                    rangeX = getMaxTextScrollX();
-                    rangeY = getMaxTextScrollY();
-                    overflingDistance = 0;
-                }
-
-                mWebViewPrivate.overScrollBy(x - oldX, y - oldY, oldX, oldY,
-                        rangeX, rangeY,
-                        overflingDistance, overflingDistance, false);
-
-                if (mOverScrollGlow != null) {
-                    mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
-                }
-            } else {
-                if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
-                    // Update the layer position instead of WebView.
-                    scrollLayerTo(x, y);
-                } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
-                    scrollEditText(x, y);
-                } else {
-                    setScrollXRaw(x);
-                    setScrollYRaw(y);
-                }
-                abortAnimation();
-                nativeSetIsScrolling(false);
-                if (!mBlockWebkitViewMessages) {
-                    WebViewCore.resumePriority();
-                    if (!mSelectingText) {
-                        WebViewCore.resumeUpdatePicture(mWebViewCore);
-                    }
-                }
-                if (oldX != getScrollX() || oldY != getScrollY()) {
-                    sendOurVisibleRect();
-                }
-            }
-        } else {
-            mWebViewPrivate.super_computeScroll();
-        }
-    }
-
-    private void scrollLayerTo(int x, int y) {
-        int dx = mScrollingLayerRect.left - x;
-        int dy = mScrollingLayerRect.top - y;
-        if ((dx == 0 && dy == 0) || mNativeClass == 0) {
-            return;
-        }
-        if (mSelectingText) {
-            if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
-                mSelectCursorBase.offset(dx, dy);
-                mSelectCursorBaseTextQuad.offset(dx, dy);
-            }
-            if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
-                mSelectCursorExtent.offset(dx, dy);
-                mSelectCursorExtentTextQuad.offset(dx, dy);
-            }
-        }
-        if (mAutoCompletePopup != null &&
-                mCurrentScrollingLayerId == mEditTextLayerId) {
-            mEditTextContentBounds.offset(dx, dy);
-            mAutoCompletePopup.resetRect();
-        }
-        nativeScrollLayer(mNativeClass, mCurrentScrollingLayerId, x, y);
-        mScrollingLayerRect.left = x;
-        mScrollingLayerRect.top = y;
-        mWebViewCore.sendMessage(WebViewCore.EventHub.SCROLL_LAYER, mCurrentScrollingLayerId,
-                mScrollingLayerRect);
-        mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
-        invalidate();
-    }
-
-    private static int computeDuration(int dx, int dy) {
-        int distance = Math.max(Math.abs(dx), Math.abs(dy));
-        int duration = distance * 1000 / STD_SPEED;
-        return Math.min(duration, MAX_DURATION);
-    }
-
-    // helper to pin the scrollBy parameters (already in view coordinates)
-    // returns true if the scroll was changed
-    private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
-        return pinScrollTo(getScrollX() + dx, getScrollY() + dy, animate, animationDuration);
-    }
-    // helper to pin the scrollTo parameters (already in view coordinates)
-    // returns true if the scroll was changed
-    private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
-        abortAnimation();
-        x = pinLocX(x);
-        y = pinLocY(y);
-        int dx = x - getScrollX();
-        int dy = y - getScrollY();
-
-        if ((dx | dy) == 0) {
-            return false;
-        }
-        if (animate) {
-            //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
-            mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
-                    animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
-            invalidate();
-        } else {
-            mWebView.scrollTo(x, y);
-        }
-        return true;
-    }
-
-    // Scale from content to view coordinates, and pin.
-    // Also called by jni webview.cpp
-    private boolean setContentScrollBy(int cx, int cy, boolean animate) {
-        if (mDrawHistory) {
-            // disallow WebView to change the scroll position as History Picture
-            // is used in the view system.
-            // TODO: as we switchOutDrawHistory when trackball or navigation
-            // keys are hit, this should be safe. Right?
-            return false;
-        }
-        cx = contentToViewDimension(cx);
-        cy = contentToViewDimension(cy);
-        if (mHeightCanMeasure) {
-            // move our visible rect according to scroll request
-            if (cy != 0) {
-                Rect tempRect = new Rect();
-                calcOurVisibleRect(tempRect);
-                tempRect.offset(cx, cy);
-                mWebView.requestRectangleOnScreen(tempRect);
-            }
-            // FIXME: We scroll horizontally no matter what because currently
-            // ScrollView and ListView will not scroll horizontally.
-            // FIXME: Why do we only scroll horizontally if there is no
-            // vertical scroll?
-//                Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
-            return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
-        } else {
-            return pinScrollBy(cx, cy, animate, 0);
-        }
-    }
-
-    /**
-     * Called by CallbackProxy when the page starts loading.
-     * @param url The URL of the page which has started loading.
-     */
-    /* package */ void onPageStarted(String url) {
-        // every time we start a new page, we want to reset the
-        // WebView certificate:  if the new site is secure, we
-        // will reload it and get a new certificate set;
-        // if the new site is not secure, the certificate must be
-        // null, and that will be the case
-        mWebView.setCertificate(null);
-
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().onPageStarted(url);
-        }
-
-        // Don't start out editing.
-        mIsEditingText = false;
-    }
-
-    /**
-     * Called by CallbackProxy when the page finishes loading.
-     * @param url The URL of the page which has finished loading.
-     */
-    /* package */ void onPageFinished(String url) {
-        mZoomManager.onPageFinished(url);
-
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().onPageFinished(url);
-        }
-    }
-
-    // scale from content to view coordinates, and pin
-    private void contentScrollTo(int cx, int cy, boolean animate) {
-        if (mDrawHistory) {
-            // disallow WebView to change the scroll position as History Picture
-            // is used in the view system.
-            return;
-        }
-        int vx = contentToViewX(cx);
-        int vy = contentToViewY(cy);
-        pinScrollTo(vx, vy, animate, 0);
-    }
-
-    /**
-     * These are from webkit, and are in content coordinate system (unzoomed)
-     */
-    private void contentSizeChanged(boolean updateLayout) {
-        // suppress 0,0 since we usually see real dimensions soon after
-        // this avoids drawing the prev content in a funny place. If we find a
-        // way to consolidate these notifications, this check may become
-        // obsolete
-        if ((mContentWidth | mContentHeight) == 0) {
-            return;
-        }
-
-        if (mHeightCanMeasure) {
-            if (mWebView.getMeasuredHeight() != contentToViewDimension(mContentHeight)
-                    || updateLayout) {
-                mWebView.requestLayout();
-            }
-        } else if (mWidthCanMeasure) {
-            if (mWebView.getMeasuredWidth() != contentToViewDimension(mContentWidth)
-                    || updateLayout) {
-                mWebView.requestLayout();
-            }
-        } else {
-            // If we don't request a layout, try to send our view size to the
-            // native side to ensure that WebCore has the correct dimensions.
-            sendViewSizeZoom(false);
-        }
-    }
-
-    /**
-     * See {@link WebView#setWebViewClient(WebViewClient)}
-     */
-    @Override
-    public void setWebViewClient(WebViewClient client) {
-        mCallbackProxy.setWebViewClient(client);
-    }
-
-    /**
-     * Gets the WebViewClient
-     * @return the current WebViewClient instance.
-     *
-     * This is an implementation detail.
-     */
-    public WebViewClient getWebViewClient() {
-        return mCallbackProxy.getWebViewClient();
-    }
-
-    /**
-     * See {@link WebView#setDownloadListener(DownloadListener)}
-     */
-    @Override
-    public void setDownloadListener(DownloadListener listener) {
-        mCallbackProxy.setDownloadListener(listener);
-    }
-
-    /**
-     * See {@link WebView#setWebChromeClient(WebChromeClient)}
-     */
-    @Override
-    public void setWebChromeClient(WebChromeClient client) {
-        mCallbackProxy.setWebChromeClient(client);
-    }
-
-    /**
-     * Gets the chrome handler.
-     * @return the current WebChromeClient instance.
-     *
-     * This is an implementation detail.
-     */
-    public WebChromeClient getWebChromeClient() {
-        return mCallbackProxy.getWebChromeClient();
-    }
-
-    /**
-     * Set the back/forward list client. This is an implementation of
-     * WebBackForwardListClient for handling new items and changes in the
-     * history index.
-     * @param client An implementation of WebBackForwardListClient.
-     */
-    public void setWebBackForwardListClient(WebBackForwardListClient client) {
-        mCallbackProxy.setWebBackForwardListClient(client);
-    }
-
-    /**
-     * Gets the WebBackForwardListClient.
-     */
-    public WebBackForwardListClient getWebBackForwardListClient() {
-        return mCallbackProxy.getWebBackForwardListClient();
-    }
-
-    /**
-     * See {@link WebView#setPictureListener(PictureListener)}
-     */
-    @Override
-    @Deprecated
-    public void setPictureListener(PictureListener listener) {
-        mPictureListener = listener;
-    }
-
-    /* FIXME: Debug only! Remove for SDK! */
-    public void externalRepresentation(Message callback) {
-        mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
-    }
-
-    /* FIXME: Debug only! Remove for SDK! */
-    public void documentAsText(Message callback) {
-        mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
-    }
-
-    /**
-     * See {@link WebView#addJavascriptInterface(Object, String)}
-     */
-    @Override
-    public void addJavascriptInterface(Object object, String name) {
-
-        if (object == null) {
-            return;
-        }
-        WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
-
-        arg.mObject = object;
-        arg.mInterfaceName = name;
-
-        // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
-        // methods that are accessible from JS.
-        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            arg.mRequireAnnotation = true;
-        } else {
-            arg.mRequireAnnotation = false;
-        }
-        mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
-    }
-
-    /**
-     * See {@link WebView#removeJavascriptInterface(String)}
-     */
-    @Override
-    public void removeJavascriptInterface(String interfaceName) {
-        if (mWebViewCore != null) {
-            WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
-            arg.mInterfaceName = interfaceName;
-            mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg);
-        }
-    }
-
-    /**
-     * See {@link WebView#getSettings()}
-     * Note this returns WebSettingsClassic, a sub-class of WebSettings, which can be used
-     * to access extension APIs.
-     */
-    @Override
-    public WebSettingsClassic getSettings() {
-        return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
-    }
-
-    /**
-     * See {@link WebView#getPluginList()}
-     */
-    @Deprecated
-    public static synchronized PluginList getPluginList() {
-        return new PluginList();
-    }
-
-    /**
-     * See {@link WebView#refreshPlugins(boolean)}
-     */
-    @Deprecated
-    public void refreshPlugins(boolean reloadOpenPages) {
-    }
-
-    //-------------------------------------------------------------------------
-    // Override View methods
-    //-------------------------------------------------------------------------
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            destroy();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private void drawContent(Canvas canvas) {
-        if (mDrawHistory) {
-            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
-            canvas.drawPicture(mHistoryPicture);
-            return;
-        }
-        if (mNativeClass == 0) return;
-
-        boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
-        boolean animateScroll = ((!mScroller.isFinished()
-                || mVelocityTracker != null)
-                && (mTouchMode != TOUCH_DRAG_MODE ||
-                mHeldMotionless != MOTIONLESS_TRUE));
-        if (mTouchMode == TOUCH_DRAG_MODE) {
-            if (mHeldMotionless == MOTIONLESS_PENDING) {
-                mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
-                mHeldMotionless = MOTIONLESS_FALSE;
-            }
-            if (mHeldMotionless == MOTIONLESS_FALSE) {
-                mPrivateHandler.sendMessageDelayed(mPrivateHandler
-                        .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
-                mHeldMotionless = MOTIONLESS_PENDING;
-            }
-        }
-        int saveCount = canvas.save();
-        if (animateZoom) {
-            mZoomManager.animateZoom(canvas);
-        } else if (!canvas.isHardwareAccelerated()) {
-            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
-        }
-
-        boolean UIAnimationsRunning = false;
-        // Currently for each draw we compute the animation values;
-        // We may in the future decide to do that independently.
-        if (mNativeClass != 0 && !canvas.isHardwareAccelerated()
-                && nativeEvaluateLayersAnimations(mNativeClass)) {
-            UIAnimationsRunning = true;
-            // If we have unfinished (or unstarted) animations,
-            // we ask for a repaint. We only need to do this in software
-            // rendering (with hardware rendering we already have a different
-            // method of requesting a repaint)
-            mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
-            invalidate();
-        }
-
-        // decide which adornments to draw
-        int extras = DRAW_EXTRAS_NONE;
-        if (!mFindIsUp && mShowTextSelectionExtra) {
-            extras = DRAW_EXTRAS_SELECTION;
-        }
-
-        calcOurContentVisibleRectF(mVisibleContentRect);
-        if (canvas.isHardwareAccelerated()) {
-            Rect invScreenRect = mIsWebViewVisible ? mInvScreenRect : null;
-            Rect screenRect = mIsWebViewVisible ? mScreenRect : null;
-
-            int functor = nativeCreateDrawGLFunction(mNativeClass, invScreenRect,
-                    screenRect, mVisibleContentRect, getScale(), extras);
-            ((HardwareCanvas) canvas).callDrawGLFunction(functor);
-            if (mHardwareAccelSkia != getSettings().getHardwareAccelSkiaEnabled()) {
-                mHardwareAccelSkia = getSettings().getHardwareAccelSkiaEnabled();
-                nativeUseHardwareAccelSkia(mHardwareAccelSkia);
-            }
-
-        } else {
-            DrawFilter df = null;
-            if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
-                df = mZoomFilter;
-            } else if (animateScroll) {
-                df = mScrollFilter;
-            }
-            canvas.setDrawFilter(df);
-            nativeDraw(canvas, mVisibleContentRect, mBackgroundColor, extras);
-            canvas.setDrawFilter(null);
-        }
-
-        canvas.restoreToCount(saveCount);
-        drawTextSelectionHandles(canvas);
-
-        if (extras == DRAW_EXTRAS_CURSOR_RING) {
-            if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
-                mTouchMode = TOUCH_SHORTPRESS_MODE;
-            }
-        }
-    }
-
-    /**
-     * Draw the background when beyond bounds
-     * @param canvas Canvas to draw into
-     */
-    private void drawOverScrollBackground(Canvas canvas) {
-        if (mOverScrollBackground == null) {
-            mOverScrollBackground = new Paint();
-            Bitmap bm = BitmapFactory.decodeResource(
-                    mContext.getResources(),
-                    com.android.internal.R.drawable.status_bar_background);
-            mOverScrollBackground.setShader(new BitmapShader(bm,
-                    Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
-            mOverScrollBorder = new Paint();
-            mOverScrollBorder.setStyle(Paint.Style.STROKE);
-            mOverScrollBorder.setStrokeWidth(0);
-            mOverScrollBorder.setColor(0xffbbbbbb);
-        }
-
-        int top = 0;
-        int right = computeRealHorizontalScrollRange();
-        int bottom = top + computeRealVerticalScrollRange();
-        // first draw the background and anchor to the top of the view
-        canvas.save();
-        canvas.translate(getScrollX(), getScrollY());
-        canvas.clipRect(-getScrollX(), top - getScrollY(), right - getScrollX(), bottom
-                - getScrollY(), Region.Op.DIFFERENCE);
-        canvas.drawPaint(mOverScrollBackground);
-        canvas.restore();
-        // then draw the border
-        canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
-        // next clip the region for the content
-        canvas.clipRect(0, top, right, bottom);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        if (inFullScreenMode()) {
-            return; // no need to draw anything if we aren't visible.
-        }
-        // if mNativeClass is 0, the WebView is either destroyed or not
-        // initialized. In either case, just draw the background color and return
-        if (mNativeClass == 0) {
-            canvas.drawColor(mBackgroundColor);
-            return;
-        }
-
-        // if both mContentWidth and mContentHeight are 0, it means there is no
-        // valid Picture passed to WebView yet. This can happen when WebView
-        // just starts. Draw the background and return.
-        if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
-            canvas.drawColor(mBackgroundColor);
-            return;
-        }
-
-        if (canvas.isHardwareAccelerated()) {
-            mZoomManager.setHardwareAccelerated();
-        } else {
-            mWebViewCore.resumeWebKitDraw();
-        }
-
-        int saveCount = canvas.save();
-        if (mInOverScrollMode && !getSettings()
-                .getUseWebViewBackgroundForOverscrollBackground()) {
-            drawOverScrollBackground(canvas);
-        }
-
-        canvas.translate(0, getTitleHeight());
-        drawContent(canvas);
-        canvas.restoreToCount(saveCount);
-
-        if (AUTO_REDRAW_HACK && mAutoRedraw) {
-            invalidate();
-        }
-        mWebViewCore.signalRepaintDone();
-
-        if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
-            invalidate();
-        }
-
-        if (mFocusTransition != null) {
-            mFocusTransition.draw(canvas);
-        } else if (shouldDrawHighlightRect()) {
-            RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
-            Rect r = new Rect();
-            while (iter.next(r)) {
-                canvas.drawRect(r, mTouchHightlightPaint);
-            }
-        }
-        if (DEBUG_TOUCH_HIGHLIGHT) {
-            if (getSettings().getNavDump()) {
-                if ((mTouchHighlightX | mTouchHighlightY) != 0) {
-                    if (mTouchCrossHairColor == null) {
-                        mTouchCrossHairColor = new Paint();
-                        mTouchCrossHairColor.setColor(Color.RED);
-                    }
-                    canvas.drawLine(mTouchHighlightX - mNavSlop,
-                            mTouchHighlightY - mNavSlop, mTouchHighlightX
-                                    + mNavSlop + 1, mTouchHighlightY + mNavSlop
-                                    + 1, mTouchCrossHairColor);
-                    canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
-                            mTouchHighlightY - mNavSlop, mTouchHighlightX
-                                    - mNavSlop,
-                            mTouchHighlightY + mNavSlop + 1,
-                            mTouchCrossHairColor);
-                }
-            }
-        }
-    }
-
-    private void removeTouchHighlight() {
-        setTouchHighlightRects(null);
-    }
-
-    @Override
-    public void setLayoutParams(ViewGroup.LayoutParams params) {
-        if (params.height == AbsoluteLayout.LayoutParams.WRAP_CONTENT) {
-            mWrapContent = true;
-        }
-        mWebViewPrivate.super_setLayoutParams(params);
-    }
-
-    @Override
-    public boolean performLongClick() {
-        // performLongClick() is the result of a delayed message. If we switch
-        // to windows overview, the WebView will be temporarily removed from the
-        // view system. In that case, do nothing.
-        if (mWebView.getParent() == null) return false;
-
-        // A multi-finger gesture can look like a long press; make sure we don't take
-        // long press actions if we're scaling.
-        final ScaleGestureDetector detector = mZoomManager.getScaleGestureDetector();
-        if (detector != null && detector.isInProgress()) {
-            return false;
-        }
-
-        if (mSelectingText) return false; // long click does nothing on selection
-        /* if long click brings up a context menu, the super function
-         * returns true and we're done. Otherwise, nothing happened when
-         * the user clicked. */
-        if (mWebViewPrivate.super_performLongClick()) {
-            return true;
-        }
-        /* In the case where the application hasn't already handled the long
-         * click action, look for a word under the  click. If one is found,
-         * animate the text selection into view.
-         * FIXME: no animation code yet */
-        final boolean isSelecting = selectText();
-        if (isSelecting) {
-            mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        } else if (focusCandidateIsEditableText()) {
-            mSelectCallback = new SelectActionModeCallback();
-            mSelectCallback.setWebView(this);
-            mSelectCallback.setTextSelected(false);
-            mWebView.startActionMode(mSelectCallback);
-        }
-        return isSelecting;
-    }
-
-    /**
-     * Select the word at the last click point.
-     *
-     * This is an implementation detail.
-     */
-    public boolean selectText() {
-        int x = viewToContentX(mLastTouchX + getScrollX());
-        int y = viewToContentY(mLastTouchY + getScrollY());
-        return selectText(x, y);
-    }
-
-    /**
-     * Select the word at the indicated content coordinates.
-     */
-    boolean selectText(int x, int y) {
-        if (mWebViewCore == null) {
-            return false;
-        }
-        mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
-        return true;
-    }
-
-    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        mCachedOverlappingActionModeHeight = -1;
-        if (mSelectingText && mOrientation != newConfig.orientation) {
-            selectionDone();
-        }
-        mOrientation = newConfig.orientation;
-        if (mWebViewCore != null && !mBlockWebkitViewMessages) {
-            mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
-        }
-    }
-
-    /**
-     * Keep track of the Callback so we can end its ActionMode or remove its
-     * titlebar.
-     */
-    private SelectActionModeCallback mSelectCallback;
-
-    void setBaseLayer(int layer, boolean showVisualIndicator,
-            boolean isPictureAfterFirstLayout) {
-        if (mNativeClass == 0)
-            return;
-        boolean queueFull;
-        final int scrollingLayer = (mTouchMode == TOUCH_DRAG_LAYER_MODE)
-                ? mCurrentScrollingLayerId : 0;
-        queueFull = nativeSetBaseLayer(mNativeClass, layer,
-                                       showVisualIndicator, isPictureAfterFirstLayout,
-                                       scrollingLayer);
-
-        if (queueFull) {
-            mWebViewCore.pauseWebKitDraw();
-        } else {
-            mWebViewCore.resumeWebKitDraw();
-        }
-
-        if (mHTML5VideoViewProxy != null) {
-            mHTML5VideoViewProxy.setBaseLayer(layer);
-        }
-    }
-
-    int getBaseLayer() {
-        if (mNativeClass == 0) {
-            return 0;
-        }
-        return nativeGetBaseLayer(mNativeClass);
-    }
-
-    private void onZoomAnimationStart() {
-    }
-
-    private void onZoomAnimationEnd() {
-        mPrivateHandler.sendEmptyMessage(RELOCATE_AUTO_COMPLETE_POPUP);
-    }
-
-    void onFixedLengthZoomAnimationStart() {
-        WebViewCore.pauseUpdatePicture(getWebViewCore());
-        onZoomAnimationStart();
-    }
-
-    void onFixedLengthZoomAnimationEnd() {
-        if (!mBlockWebkitViewMessages && !mSelectingText) {
-            WebViewCore.resumeUpdatePicture(mWebViewCore);
-        }
-        onZoomAnimationEnd();
-    }
-
-    private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
-                                         Paint.DITHER_FLAG |
-                                         Paint.SUBPIXEL_TEXT_FLAG;
-    private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
-                                           Paint.DITHER_FLAG;
-
-    private final DrawFilter mZoomFilter =
-            new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
-    // If we need to trade better quality for speed, set mScrollFilter to null
-    private final DrawFilter mScrollFilter =
-            new PaintFlagsDrawFilter(SCROLL_BITS, 0);
-
-    private class SelectionHandleAlpha {
-        private int mAlpha = 0;
-        private int mTargetAlpha = 0;
-
-        public void setAlpha(int alpha) {
-            mAlpha = alpha;
-            // TODO: Use partial invalidate
-            invalidate();
-        }
-
-        public int getAlpha() {
-            return mAlpha;
-        }
-
-        public void setTargetAlpha(int alpha) {
-            mTargetAlpha = alpha;
-        }
-
-        public int getTargetAlpha() {
-            return mTargetAlpha;
-        }
-
-    }
-
-    private void startSelectingText() {
-        mSelectingText = true;
-        mShowTextSelectionExtra = true;
-        animateHandles();
-    }
-
-    private void animateHandle(boolean canShow, ObjectAnimator animator,
-            Point selectionPoint, int selectionLayerId,
-            SelectionHandleAlpha alpha) {
-        boolean isVisible = canShow && mSelectingText
-                && ((mSelectionStarted && mSelectDraggingCursor == selectionPoint)
-                || isHandleVisible(selectionPoint, selectionLayerId));
-        int targetValue = isVisible ? 255 : 0;
-        if (targetValue != alpha.getTargetAlpha()) {
-            alpha.setTargetAlpha(targetValue);
-            animator.setIntValues(targetValue);
-            animator.setDuration(SELECTION_HANDLE_ANIMATION_MS);
-            animator.start();
-        }
-    }
-
-    private void animateHandles() {
-        boolean canShowBase = mSelectingText;
-        boolean canShowExtent = mSelectingText && !mIsCaretSelection;
-        animateHandle(canShowBase, mBaseHandleAlphaAnimator, mSelectCursorBase,
-                mSelectCursorBaseLayerId, mBaseAlpha);
-        animateHandle(canShowExtent, mExtentHandleAlphaAnimator,
-                mSelectCursorExtent, mSelectCursorExtentLayerId,
-                mExtentAlpha);
-    }
-
-    private void endSelectingText() {
-        mSelectingText = false;
-        mShowTextSelectionExtra = false;
-        animateHandles();
-    }
-
-    private void ensureSelectionHandles() {
-        if (mSelectHandleCenter == null) {
-            mSelectHandleCenter = mContext.getResources().getDrawable(
-                    com.android.internal.R.drawable.text_select_handle_middle).mutate();
-            mSelectHandleLeft = mContext.getResources().getDrawable(
-                    com.android.internal.R.drawable.text_select_handle_left).mutate();
-            mSelectHandleRight = mContext.getResources().getDrawable(
-                    com.android.internal.R.drawable.text_select_handle_right).mutate();
-            // All handles have the same height, so we can save effort with
-            // this assumption.
-            mSelectOffset = new Point(0,
-                    -mSelectHandleLeft.getIntrinsicHeight());
-        }
-    }
-
-    private void drawHandle(Point point, int handleId, Rect bounds,
-            int alpha, Canvas canvas) {
-        int offset;
-        int width;
-        int height;
-        Drawable drawable;
-        boolean isLeft = nativeIsHandleLeft(mNativeClass, handleId);
-        if (isLeft) {
-            drawable = mSelectHandleLeft;
-            width = mSelectHandleLeft.getIntrinsicWidth();
-            height = mSelectHandleLeft.getIntrinsicHeight();
-            // Magic formula copied from TextView
-            offset = (width * 3) / 4;
-        } else {
-            drawable = mSelectHandleRight;
-            width = mSelectHandleRight.getIntrinsicWidth();
-            height = mSelectHandleRight.getIntrinsicHeight();
-            // Magic formula copied from TextView
-            offset = width / 4;
-        }
-        int x = contentToViewDimension(point.x);
-        int y = contentToViewDimension(point.y);
-        bounds.set(x - offset, y, x - offset + width, y + height);
-        drawable.setBounds(bounds);
-        drawable.setAlpha(alpha);
-        drawable.draw(canvas);
-    }
-
-    private void drawTextSelectionHandles(Canvas canvas) {
-        if (mBaseAlpha.getAlpha() == 0 && mExtentAlpha.getAlpha() == 0) {
-            return;
-        }
-        ensureSelectionHandles();
-        if (mIsCaretSelection) {
-            // Caret handle is centered
-            int x = contentToViewDimension(mSelectCursorBase.x) -
-                    (mSelectHandleCenter.getIntrinsicWidth() / 2);
-            int y = contentToViewDimension(mSelectCursorBase.y);
-            mSelectHandleBaseBounds.set(x, y,
-                    x + mSelectHandleCenter.getIntrinsicWidth(),
-                    y + mSelectHandleCenter.getIntrinsicHeight());
-            mSelectHandleCenter.setBounds(mSelectHandleBaseBounds);
-            mSelectHandleCenter.setAlpha(mBaseAlpha.getAlpha());
-            mSelectHandleCenter.draw(canvas);
-        } else {
-            drawHandle(mSelectCursorBase, HANDLE_ID_BASE,
-                    mSelectHandleBaseBounds, mBaseAlpha.getAlpha(), canvas);
-            drawHandle(mSelectCursorExtent, HANDLE_ID_EXTENT,
-                    mSelectHandleExtentBounds, mExtentAlpha.getAlpha(), canvas);
-        }
-    }
-
-    private boolean isHandleVisible(Point selectionPoint, int layerId) {
-        boolean isVisible = true;
-        if (mIsEditingText) {
-            isVisible = mEditTextContentBounds.contains(selectionPoint.x,
-                    selectionPoint.y);
-        }
-        if (isVisible) {
-            isVisible = nativeIsPointVisible(mNativeClass, layerId,
-                    selectionPoint.x, selectionPoint.y);
-        }
-        return isVisible;
-    }
-
-    /**
-     * Takes an int[4] array as an output param with the values being
-     * startX, startY, endX, endY
-     */
-    private void getSelectionHandles(int[] handles) {
-        handles[0] = mSelectCursorBase.x;
-        handles[1] = mSelectCursorBase.y;
-        handles[2] = mSelectCursorExtent.x;
-        handles[3] = mSelectCursorExtent.y;
-    }
-
-    // draw history
-    private boolean mDrawHistory = false;
-    private Picture mHistoryPicture = null;
-    private int mHistoryWidth = 0;
-    private int mHistoryHeight = 0;
-
-    // Only check the flag, can be called from WebCore thread
-    boolean drawHistory() {
-        return mDrawHistory;
-    }
-
-    int getHistoryPictureWidth() {
-        return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
-    }
-
-    // Should only be called in UI thread
-    void switchOutDrawHistory() {
-        if (null == mWebViewCore) return; // CallbackProxy may trigger this
-        if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
-            mDrawHistory = false;
-            mHistoryPicture = null;
-            invalidate();
-            int oldScrollX = getScrollX();
-            int oldScrollY = getScrollY();
-            setScrollXRaw(pinLocX(getScrollX()));
-            setScrollYRaw(pinLocY(getScrollY()));
-            if (oldScrollX != getScrollX() || oldScrollY != getScrollY()) {
-                mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldScrollX, oldScrollY);
-            } else {
-                sendOurVisibleRect();
-            }
-        }
-    }
-
-    /**
-     *  Delete text from start to end in the focused textfield. If there is no
-     *  focus, or if start == end, silently fail.  If start and end are out of
-     *  order, swap them.
-     *  @param  start   Beginning of selection to delete.
-     *  @param  end     End of selection to delete.
-     */
-    /* package */ void deleteSelection(int start, int end) {
-        mTextGeneration++;
-        WebViewCore.TextSelectionData data
-                = new WebViewCore.TextSelectionData(start, end, 0);
-        mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
-                data);
-    }
-
-    /**
-     *  Set the selection to (start, end) in the focused textfield. If start and
-     *  end are out of order, swap them.
-     *  @param  start   Beginning of selection.
-     *  @param  end     End of selection.
-     */
-    /* package */ void setSelection(int start, int end) {
-        if (mWebViewCore != null) {
-            mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
-        }
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        if (mInputConnection == null) {
-            mInputConnection = new WebViewInputConnection();
-            mAutoCompletePopup = new AutoCompletePopup(this, mInputConnection);
-        }
-        mInputConnection.setupEditorInfo(outAttrs);
-        return mInputConnection;
-    }
-
-    private void relocateAutoCompletePopup() {
-        if (mAutoCompletePopup != null) {
-            mAutoCompletePopup.resetRect();
-            mAutoCompletePopup.setText(mInputConnection.getEditable());
-        }
-    }
-
-    /**
-     * Called in response to a message from webkit telling us that the soft
-     * keyboard should be launched.
-     */
-    private void displaySoftKeyboard(boolean isTextView) {
-        InputMethodManager imm = (InputMethodManager)
-                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
-
-        // bring it back to the default level scale so that user can enter text
-        boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
-        if (zoom) {
-            mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
-            mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
-        }
-        // Used by plugins and contentEditable.
-        // Also used if the navigation cache is out of date, and
-        // does not recognize that a textfield is in focus.  In that
-        // case, use WebView as the targeted view.
-        // see http://b/issue?id=2457459
-        imm.showSoftInput(mWebView, 0);
-    }
-
-    // Called by WebKit to instruct the UI to hide the keyboard
-    private void hideSoftKeyboard() {
-        InputMethodManager imm = InputMethodManager.peekInstance();
-        if (imm != null && (imm.isActive(mWebView))) {
-            imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
-        }
-    }
-
-    /**
-     * Called by AutoCompletePopup to find saved form data associated with the
-     * textfield
-     * @param name Name of the textfield.
-     * @param nodePointer Pointer to the node of the textfield, so it can be
-     *          compared to the currently focused textfield when the data is
-     *          retrieved.
-     * @param autoFillable true if WebKit has determined this field is part of
-     *          a form that can be auto filled.
-     * @param autoComplete true if the attribute "autocomplete" is set to true
-     *          on the textfield.
-     */
-    /* package */ void requestFormData(String name, int nodePointer,
-            boolean autoFillable, boolean autoComplete) {
-        if (mWebViewCore.getSettings().getSaveFormData()) {
-            Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
-            update.arg1 = nodePointer;
-            RequestFormData updater = new RequestFormData(name, getUrl(),
-                    update, autoFillable, autoComplete);
-            Thread t = new Thread(updater);
-            t.start();
-        }
-    }
-
-    /*
-     * This class requests an Adapter for the AutoCompletePopup which shows past
-     * entries stored in the database.  It is a Runnable so that it can be done
-     * in its own thread, without slowing down the UI.
-     */
-    private class RequestFormData implements Runnable {
-        private String mName;
-        private String mUrl;
-        private Message mUpdateMessage;
-        private boolean mAutoFillable;
-        private boolean mAutoComplete;
-        private WebSettingsClassic mWebSettings;
-
-        public RequestFormData(String name, String url, Message msg,
-                boolean autoFillable, boolean autoComplete) {
-            mName = name;
-            mUrl = WebTextView.urlForAutoCompleteData(url);
-            mUpdateMessage = msg;
-            mAutoFillable = autoFillable;
-            mAutoComplete = autoComplete;
-            mWebSettings = getSettings();
-        }
-
-        @Override
-        public void run() {
-            ArrayList<String> pastEntries = new ArrayList<String>();
-
-            if (mAutoFillable) {
-                // Note that code inside the adapter click handler in AutoCompletePopup depends
-                // on the AutoFill item being at the top of the drop down list. If you change
-                // the order, make sure to do it there too!
-                if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) {
-                    pastEntries.add(mWebView.getResources().getText(
-                            com.android.internal.R.string.autofill_this_form).toString() +
-                            " " +
-                    mAutoFillData.getPreviewString());
-                    mAutoCompletePopup.setIsAutoFillProfileSet(true);
-                } else {
-                    // There is no autofill profile set up yet, so add an option that
-                    // will invite the user to set their profile up.
-                    pastEntries.add(mWebView.getResources().getText(
-                            com.android.internal.R.string.setup_autofill).toString());
-                    mAutoCompletePopup.setIsAutoFillProfileSet(false);
-                }
-            }
-
-            if (mAutoComplete) {
-                pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
-            }
-
-            if (pastEntries.size() > 0) {
-                ArrayAdapter<String> adapter = new ArrayAdapter<String>(
-                        mContext,
-                        com.android.internal.R.layout.web_text_view_dropdown,
-                        pastEntries);
-                mUpdateMessage.obj = adapter;
-                mUpdateMessage.sendToTarget();
-            }
-        }
-    }
-
-    /**
-     * Dump the display tree to "/sdcard/displayTree.txt"
-     *
-     * debug only
-     */
-    public void dumpDisplayTree() {
-        nativeDumpDisplayTree(getUrl());
-    }
-
-    /**
-     * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
-     * "/sdcard/domTree.txt"
-     *
-     * debug only
-     */
-    public void dumpDomTree(boolean toFile) {
-        mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
-    }
-
-    /**
-     * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
-     * to "/sdcard/renderTree.txt"
-     *
-     * debug only
-     */
-    public void dumpRenderTree(boolean toFile) {
-        mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
-    }
-
-    /**
-     * Called by DRT on UI thread, need to proxy to WebCore thread.
-     *
-     * debug only
-     */
-    public void setUseMockDeviceOrientation() {
-        mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
-    }
-
-    /**
-     * Sets use of the Geolocation mock client. Also resets that client. Called
-     * by DRT on UI thread, need to proxy to WebCore thread.
-     *
-     * debug only
-     */
-    public void setUseMockGeolocation() {
-        mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_GEOLOCATION);
-    }
-
-    /**
-     * Called by DRT on WebCore thread.
-     *
-     * debug only
-     */
-    public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
-        mWebViewCore.setMockGeolocationPosition(latitude, longitude, accuracy);
-    }
-
-    /**
-     * Called by DRT on WebCore thread.
-     *
-     * debug only
-     */
-    public void setMockGeolocationError(int code, String message) {
-        mWebViewCore.setMockGeolocationError(code, message);
-    }
-
-    /**
-     * Called by DRT on WebCore thread.
-     *
-     * debug only
-     */
-    public void setMockGeolocationPermission(boolean allow) {
-        mWebViewCore.setMockGeolocationPermission(allow);
-    }
-
-    /**
-     * Called by DRT on WebCore thread.
-     *
-     * debug only
-     */
-    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
-            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
-        mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
-                canProvideGamma, gamma);
-    }
-
-    // This is used to determine long press with the center key.  Does not
-    // affect long press with the trackball/touch.
-    private boolean mGotCenterDown = false;
-
-    @Override
-    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
-        if (mBlockWebkitViewMessages) {
-            return false;
-        }
-        // send complex characters to webkit for use by JS and plugins
-        if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
-            // pass the key to DOM
-            sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
-            sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
-            // return true as DOM handles the key
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isEnterActionKey(int keyCode) {
-        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
-                || keyCode == KeyEvent.KEYCODE_ENTER
-                || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
-    }
-
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        if (mAutoCompletePopup != null) {
-            return mAutoCompletePopup.onKeyPreIme(keyCode, event);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
-                    + "keyCode=" + keyCode
-                    + ", " + event + ", unicode=" + event.getUnicodeChar());
-        }
-        if (mIsCaretSelection) {
-            selectionDone();
-        }
-        if (mBlockWebkitViewMessages) {
-            return false;
-        }
-
-        // don't implement accelerator keys here; defer to host application
-        if (event.isCtrlPressed()) {
-            return false;
-        }
-
-        if (mNativeClass == 0) {
-            return false;
-        }
-
-        // do this hack up front, so it always works, regardless of touch-mode
-        if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
-            mAutoRedraw = !mAutoRedraw;
-            if (mAutoRedraw) {
-                invalidate();
-            }
-            return true;
-        }
-
-        // Bubble up the key event if
-        // 1. it is a system key; or
-        // 2. the host application wants to handle it;
-        if (event.isSystem()
-                || mCallbackProxy.uiOverrideKeyEvent(event)) {
-            return false;
-        }
-
-        // See if the accessibility injector needs to handle this event.
-        if (isAccessibilityInjectionEnabled()
-                && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
-            return true;
-        }
-
-        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
-            if (event.hasNoModifiers()) {
-                pageUp(false);
-                return true;
-            } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
-                pageUp(true);
-                return true;
-            }
-        }
-
-        if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
-            if (event.hasNoModifiers()) {
-                pageDown(false);
-                return true;
-            } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
-                pageDown(true);
-                return true;
-            }
-        }
-
-        if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) {
-            pageUp(true);
-            return true;
-        }
-
-        if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) {
-            pageDown(true);
-            return true;
-        }
-
-        if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
-                && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-            switchOutDrawHistory();
-        }
-
-        if (isEnterActionKey(keyCode)) {
-            switchOutDrawHistory();
-            if (event.getRepeatCount() == 0) {
-                if (mSelectingText) {
-                    return true; // discard press if copy in progress
-                }
-                mGotCenterDown = true;
-                mPrivateHandler.sendMessageDelayed(mPrivateHandler
-                        .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
-            }
-        }
-
-        if (getSettings().getNavDump()) {
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_4:
-                    dumpDisplayTree();
-                    break;
-                case KeyEvent.KEYCODE_5:
-                case KeyEvent.KEYCODE_6:
-                    dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
-                    break;
-                case KeyEvent.KEYCODE_7:
-                case KeyEvent.KEYCODE_8:
-                    dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
-                    break;
-            }
-        }
-
-        // pass the key to DOM
-        sendKeyEvent(event);
-        // return true as DOM handles the key
-        return true;
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
-                    + ", " + event + ", unicode=" + event.getUnicodeChar());
-        }
-        if (mBlockWebkitViewMessages) {
-            return false;
-        }
-
-        if (mNativeClass == 0) {
-            return false;
-        }
-
-        // special CALL handling when cursor node's href is "tel:XXX"
-        if (keyCode == KeyEvent.KEYCODE_CALL
-                && mInitialHitTestResult != null
-                && mInitialHitTestResult.getType() == HitTestResult.PHONE_TYPE) {
-            String text = mInitialHitTestResult.getExtra();
-            Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
-            mContext.startActivity(intent);
-            return true;
-        }
-
-        // Bubble up the key event if
-        // 1. it is a system key; or
-        // 2. the host application wants to handle it;
-        if (event.isSystem()
-                || mCallbackProxy.uiOverrideKeyEvent(event)) {
-            return false;
-        }
-
-        // See if the accessibility injector needs to handle this event.
-        if (isAccessibilityInjectionEnabled()
-                && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
-            return true;
-        }
-
-        if (isEnterActionKey(keyCode)) {
-            // remove the long press message first
-            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
-            mGotCenterDown = false;
-
-            if (mSelectingText) {
-                copySelection();
-                selectionDone();
-                return true; // discard press if copy in progress
-            }
-        }
-
-        // pass the key to DOM
-        sendKeyEvent(event);
-        // return true as DOM handles the key
-        return true;
-    }
-
-    private boolean startSelectActionMode() {
-        mSelectCallback = new SelectActionModeCallback();
-        mSelectCallback.setTextSelected(!mIsCaretSelection);
-        mSelectCallback.setWebView(this);
-        if (mWebView.startActionMode(mSelectCallback) == null) {
-            // There is no ActionMode, so do not allow the user to modify a
-            // selection.
-            selectionDone();
-            return false;
-        }
-        mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        return true;
-    }
-
-    private void showPasteWindow() {
-        ClipboardManager cm = (ClipboardManager)(mContext
-                .getSystemService(Context.CLIPBOARD_SERVICE));
-        if (cm.hasPrimaryClip()) {
-            Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
-                    contentToViewY(mSelectCursorBase.y));
-            Point cursorTop = calculateBaseCaretTop();
-            cursorTop.set(contentToViewX(cursorTop.x),
-                    contentToViewY(cursorTop.y));
-
-            int[] location = new int[2];
-            mWebView.getLocationInWindow(location);
-            int offsetX = location[0] - getScrollX();
-            int offsetY = location[1] - getScrollY();
-            cursorPoint.offset(offsetX, offsetY);
-            cursorTop.offset(offsetX, offsetY);
-            if (mPasteWindow == null) {
-                mPasteWindow = new PastePopupWindow();
-            }
-            mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]);
-        }
-    }
-
-    /**
-     * Given segment AB, this finds the point C along AB that is closest to
-     * point and then returns it scale along AB. The scale factor is AC/AB.
-     *
-     * @param x The x coordinate of the point near segment AB that determines
-     * the scale factor.
-     * @param y The y coordinate of the point near segment AB that determines
-     * the scale factor.
-     * @param a The first point of the line segment.
-     * @param b The second point of the line segment.
-     * @return The scale factor AC/AB, where C is the point on AB closest to
-     *         point.
-     */
-    private static float scaleAlongSegment(int x, int y, PointF a, PointF b) {
-        // The bottom line of the text box is line AB
-        float abX = b.x - a.x;
-        float abY = b.y - a.y;
-        float ab2 = (abX * abX) + (abY * abY);
-
-        // The line from first point in text bounds to bottom is AP
-        float apX = x - a.x;
-        float apY = y - a.y;
-        float abDotAP = (apX * abX) + (apY * abY);
-        float scale = abDotAP / ab2;
-        return scale;
-    }
-
-    private Point calculateBaseCaretTop() {
-        return calculateCaretTop(mSelectCursorBase, mSelectCursorBaseTextQuad);
-    }
-
-    private Point calculateDraggingCaretTop() {
-        return calculateCaretTop(mSelectDraggingCursor, mSelectDraggingTextQuad);
-    }
-
-    /**
-     * Assuming arbitrary shape of a quadralateral forming text bounds, this
-     * calculates the top of a caret.
-     */
-    private static Point calculateCaretTop(Point base, QuadF quad) {
-        float scale = scaleAlongSegment(base.x, base.y, quad.p4, quad.p3);
-        int x = Math.round(scaleCoordinate(scale, quad.p1.x, quad.p2.x));
-        int y = Math.round(scaleCoordinate(scale, quad.p1.y, quad.p2.y));
-        return new Point(x, y);
-    }
-
-    private void hidePasteButton() {
-        if (mPasteWindow != null) {
-            mPasteWindow.hide();
-        }
-    }
-
-    private void syncSelectionCursors() {
-        mSelectCursorBaseLayerId =
-                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
-                        mSelectCursorBase, mSelectCursorBaseTextQuad);
-        mSelectCursorExtentLayerId =
-                nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
-                        mSelectCursorExtent, mSelectCursorExtentTextQuad);
-    }
-
-    private boolean setupWebkitSelect() {
-        syncSelectionCursors();
-        if (!mIsCaretSelection && !startSelectActionMode()) {
-            selectionDone();
-            return false;
-        }
-        startSelectingText();
-        mTouchMode = TOUCH_DRAG_MODE;
-        return true;
-    }
-
-    private void updateWebkitSelection(boolean isSnapped) {
-        int handleId = (mSelectDraggingCursor == mSelectCursorBase)
-                ? HANDLE_ID_BASE : HANDLE_ID_EXTENT;
-        int x = mSelectDraggingCursor.x;
-        int y = mSelectDraggingCursor.y;
-        if (isSnapped) {
-            // "center" the cursor in the snapping quad
-            Point top = calculateDraggingCaretTop();
-            x = Math.round((top.x + x) / 2);
-            y = Math.round((top.y + y) / 2);
-        }
-        mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
-        mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT,
-                x, y, (Integer)handleId);
-    }
-
-    private void resetCaretTimer() {
-        mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
-        if (!mSelectionStarted) {
-            mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
-                    CARET_HANDLE_STAMINA_MS);
-        }
-    }
-
-    /**
-     * Select all of the text in this WebView.
-     *
-     * This is an implementation detail.
-     */
-    public void selectAll() {
-        mWebViewCore.sendMessage(EventHub.SELECT_ALL);
-    }
-
-    /**
-     * Called when the selection has been removed.
-     */
-    void selectionDone() {
-        if (mSelectingText) {
-            hidePasteButton();
-            endSelectingText();
-            // finish is idempotent, so this is fine even if selectionDone was
-            // called by mSelectCallback.onDestroyActionMode
-            if (mSelectCallback != null) {
-                mSelectCallback.finish();
-                mSelectCallback = null;
-            }
-            invalidate(); // redraw without selection
-            mAutoScrollX = 0;
-            mAutoScrollY = 0;
-            mSentAutoScrollMessage = false;
-        }
-    }
-
-    /**
-     * Copy the selection to the clipboard
-     *
-     * This is an implementation detail.
-     */
-    public boolean copySelection() {
-        boolean copiedSomething = false;
-        String selection = getSelection();
-        if (selection != null && selection != "") {
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "copySelection \"" + selection + "\"");
-            }
-            Toast.makeText(mContext
-                    , com.android.internal.R.string.text_copied
-                    , Toast.LENGTH_SHORT).show();
-            copiedSomething = true;
-            ClipboardManager cm = (ClipboardManager)mContext
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-            cm.setText(selection);
-            int[] handles = new int[4];
-            getSelectionHandles(handles);
-            mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
-        }
-        invalidate(); // remove selection region and pointer
-        return copiedSomething;
-    }
-
-    /**
-     * Cut the selected text into the clipboard
-     *
-     * This is an implementation detail
-     */
-    public void cutSelection() {
-        copySelection();
-        int[] handles = new int[4];
-        getSelectionHandles(handles);
-        mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
-    }
-
-    /**
-     * Paste text from the clipboard to the cursor position.
-     *
-     * This is an implementation detail
-     */
-    public void pasteFromClipboard() {
-        ClipboardManager cm = (ClipboardManager)mContext
-                .getSystemService(Context.CLIPBOARD_SERVICE);
-        ClipData clipData = cm.getPrimaryClip();
-        if (clipData != null) {
-            ClipData.Item clipItem = clipData.getItemAt(0);
-            CharSequence pasteText = clipItem.coerceToText(mContext);
-            if (mInputConnection != null) {
-                mInputConnection.replaceSelection(pasteText);
-            }
-        }
-    }
-
-    /**
-     * Returns the currently highlighted text as a string.
-     */
-    String getSelection() {
-        if (mNativeClass == 0) return "";
-        return nativeGetSelection();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        if (mWebView.hasWindowFocus()) setActive(true);
-
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().toggleAccessibilityFeedback(true);
-        }
-
-        updateHwAccelerated();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        clearHelpers();
-        mZoomManager.dismissZoomPicker();
-        if (mWebView.hasWindowFocus()) setActive(false);
-
-        if (isAccessibilityInjectionEnabled()) {
-            getAccessibilityInjector().toggleAccessibilityFeedback(false);
-        }
-
-        updateHwAccelerated();
-
-        ensureFunctorDetached();
-    }
-
-    @Override
-    public void onVisibilityChanged(View changedView, int visibility) {
-        // The zoomManager may be null if the webview is created from XML that
-        // specifies the view's visibility param as not visible (see http://b/2794841)
-        if (visibility != View.VISIBLE && mZoomManager != null) {
-            mZoomManager.dismissZoomPicker();
-        }
-        updateDrawingState();
-    }
-
-    void setActive(boolean active) {
-        if (active) {
-            if (mWebView.hasFocus()) {
-                // If our window regained focus, and we have focus, then begin
-                // drawing the cursor ring
-                mDrawCursorRing = true;
-                setFocusControllerActive(true);
-            } else {
-                mDrawCursorRing = false;
-                setFocusControllerActive(false);
-            }
-        } else {
-            if (!mZoomManager.isZoomPickerVisible()) {
-                /*
-                 * The external zoom controls come in their own window, so our
-                 * window loses focus. Our policy is to not draw the cursor ring
-                 * if our window is not focused, but this is an exception since
-                 * the user can still navigate the web page with the zoom
-                 * controls showing.
-                 */
-                mDrawCursorRing = false;
-            }
-            mKeysPressed.clear();
-            mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-            mTouchMode = TOUCH_DONE_MODE;
-            setFocusControllerActive(false);
-        }
-        invalidate();
-    }
-
-    // To avoid drawing the cursor ring, and remove the TextView when our window
-    // loses focus.
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        setActive(hasWindowFocus);
-        if (hasWindowFocus) {
-            JWebCoreJavaBridge.setActiveWebView(this);
-            if (mPictureUpdatePausedForFocusChange) {
-                WebViewCore.resumeUpdatePicture(mWebViewCore);
-                mPictureUpdatePausedForFocusChange = false;
-            }
-        } else {
-            JWebCoreJavaBridge.removeActiveWebView(this);
-            final WebSettings settings = getSettings();
-            if (settings != null && settings.enableSmoothTransition() &&
-                    mWebViewCore != null && !WebViewCore.isUpdatePicturePaused(mWebViewCore)) {
-                WebViewCore.pauseUpdatePicture(mWebViewCore);
-                mPictureUpdatePausedForFocusChange = true;
-            }
-        }
-    }
-
-    /*
-     * Pass a message to WebCore Thread, telling the WebCore::Page's
-     * FocusController to be  "inactive" so that it will
-     * not draw the blinking cursor.  It gets set to "active" to draw the cursor
-     * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
-     */
-    /* package */ void setFocusControllerActive(boolean active) {
-        if (mWebViewCore == null) return;
-        mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
-        // Need to send this message after the document regains focus.
-        if (active && mListBoxMessage != null) {
-            mWebViewCore.sendMessage(mListBoxMessage);
-            mListBoxMessage = null;
-        }
-    }
-
-    @Override
-    public void onFocusChanged(boolean focused, int direction,
-            Rect previouslyFocusedRect) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
-        }
-        if (focused) {
-            mDrawCursorRing = true;
-            setFocusControllerActive(true);
-        } else {
-            mDrawCursorRing = false;
-            setFocusControllerActive(false);
-            mKeysPressed.clear();
-        }
-        if (!mTouchHighlightRegion.isEmpty()) {
-            mWebView.invalidate(mTouchHighlightRegion.getBounds());
-        }
-    }
-
-    // updateRectsForGL() happens almost every draw call, in order to avoid creating
-    // any object in this code path, we move the local variable out to be a private
-    // final member, and we marked them as mTemp*.
-    private final Point mTempVisibleRectOffset = new Point();
-    private final Rect mTempVisibleRect = new Rect();
-
-    void updateRectsForGL() {
-        // Use the getGlobalVisibleRect() to get the intersection among the parents
-        // visible == false means we're clipped - send a null rect down to indicate that
-        // we should not draw
-        boolean visible = mWebView.getGlobalVisibleRect(mTempVisibleRect, mTempVisibleRectOffset);
-        mInvScreenRect.set(mTempVisibleRect);
-        if (visible) {
-            // Then need to invert the Y axis, just for GL
-            View rootView = mWebView.getRootView();
-            int rootViewHeight = rootView.getHeight();
-            mScreenRect.set(mInvScreenRect);
-            int savedWebViewBottom = mInvScreenRect.bottom;
-            mInvScreenRect.bottom = rootViewHeight - mInvScreenRect.top - getVisibleTitleHeightImpl();
-            mInvScreenRect.top = rootViewHeight - savedWebViewBottom;
-            mIsWebViewVisible = true;
-        } else {
-            mIsWebViewVisible = false;
-        }
-
-        mTempVisibleRect.offset(-mTempVisibleRectOffset.x, -mTempVisibleRectOffset.y);
-        viewToContentVisibleRect(mVisibleContentRect, mTempVisibleRect);
-
-        nativeUpdateDrawGLFunction(mNativeClass, mIsWebViewVisible ? mInvScreenRect : null,
-                mIsWebViewVisible ? mScreenRect : null,
-                mVisibleContentRect, getScale());
-    }
-
-    // Input : viewRect, rect in view/screen coordinate.
-    // Output: contentRect, rect in content/document coordinate.
-    private void viewToContentVisibleRect(RectF contentRect, Rect viewRect) {
-        contentRect.left = viewToContentXf(viewRect.left) / mWebView.getScaleX();
-        // viewToContentY will remove the total height of the title bar.  Add
-        // the visible height back in to account for the fact that if the title
-        // bar is partially visible, the part of the visible rect which is
-        // displaying our content is displaced by that amount.
-        contentRect.top = viewToContentYf(viewRect.top + getVisibleTitleHeightImpl())
-                / mWebView.getScaleY();
-        contentRect.right = viewToContentXf(viewRect.right) / mWebView.getScaleX();
-        contentRect.bottom = viewToContentYf(viewRect.bottom) / mWebView.getScaleY();
-    }
-
-    @Override
-    public boolean setFrame(int left, int top, int right, int bottom) {
-        boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom);
-        if (!changed && mHeightCanMeasure) {
-            // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
-            // in WebViewCore after we get the first layout. We do call
-            // requestLayout() when we get contentSizeChanged(). But the View
-            // system won't call onSizeChanged if the dimension is not changed.
-            // In this case, we need to call sendViewSizeZoom() explicitly to
-            // notify the WebKit about the new dimensions.
-            sendViewSizeZoom(false);
-        }
-        updateRectsForGL();
-        return changed;
-    }
-
-    @Override
-    public void onSizeChanged(int w, int h, int ow, int oh) {
-        // adjust the max viewport width depending on the view dimensions. This
-        // is to ensure the scaling is not going insane. So do not shrink it if
-        // the view size is temporarily smaller, e.g. when soft keyboard is up.
-        int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
-        if (newMaxViewportWidth > sMaxViewportWidth) {
-            sMaxViewportWidth = newMaxViewportWidth;
-        }
-
-        mZoomManager.onSizeChanged(w, h, ow, oh);
-
-        if (mLoadedPicture != null && mDelaySetPicture == null) {
-            // Size changes normally result in a new picture
-            // Re-set the loaded picture to simulate that
-            // However, do not update the base layer as that hasn't changed
-            setNewPicture(mLoadedPicture, false);
-        }
-        if (mIsEditingText) {
-            scrollEditIntoView();
-        }
-        relocateAutoCompletePopup();
-    }
-
-    /**
-     * Scrolls the edit field into view using the minimum scrolling necessary.
-     * If the edit field is too large to fit in the visible window, the caret
-     * dimensions are used so that at least the caret is visible.
-     * A buffer of EDIT_RECT_BUFFER in view pixels is used to offset the
-     * edit rectangle to ensure a margin with the edge of the screen.
-     */
-    private void scrollEditIntoView() {
-        Rect visibleRect = new Rect(viewToContentX(getScrollX()),
-                viewToContentY(getScrollY()),
-                viewToContentX(getScrollX() + getWidth()),
-                viewToContentY(getScrollY() + getViewHeightWithTitle()));
-        if (visibleRect.contains(mEditTextContentBounds)) {
-            return; // no need to scroll
-        }
-        syncSelectionCursors();
-        nativeFindMaxVisibleRect(mNativeClass, mEditTextLayerId, visibleRect);
-        final int buffer = Math.max(1, viewToContentDimension(EDIT_RECT_BUFFER));
-        Rect showRect = new Rect(
-                Math.max(0, mEditTextContentBounds.left - buffer),
-                Math.max(0, mEditTextContentBounds.top - buffer),
-                mEditTextContentBounds.right + buffer,
-                mEditTextContentBounds.bottom + buffer);
-        Point caretTop = calculateBaseCaretTop();
-        if (visibleRect.width() < mEditTextContentBounds.width()) {
-            // The whole edit won't fit in the width, so use the caret rect
-            if (mSelectCursorBase.x < caretTop.x) {
-                showRect.left = Math.max(0, mSelectCursorBase.x - buffer);
-                showRect.right = caretTop.x + buffer;
-            } else {
-                showRect.left = Math.max(0, caretTop.x - buffer);
-                showRect.right = mSelectCursorBase.x + buffer;
-            }
-        }
-        if (visibleRect.height() < mEditTextContentBounds.height()) {
-            // The whole edit won't fit in the height, so use the caret rect
-            if (mSelectCursorBase.y > caretTop.y) {
-                showRect.top = Math.max(0, caretTop.y - buffer);
-                showRect.bottom = mSelectCursorBase.y + buffer;
-            } else {
-                showRect.top = Math.max(0, mSelectCursorBase.y - buffer);
-                showRect.bottom = caretTop.y + buffer;
-            }
-        }
-
-        if (visibleRect.contains(showRect)) {
-            return; // no need to scroll
-        }
-
-        int scrollX = viewToContentX(getScrollX());
-        if (visibleRect.left > showRect.left) {
-            // We are scrolled too far
-            scrollX += showRect.left - visibleRect.left;
-        } else if (visibleRect.right < showRect.right) {
-            // We aren't scrolled enough to include the right
-            scrollX += showRect.right - visibleRect.right;
-        }
-        int scrollY = viewToContentY(getScrollY());
-        if (visibleRect.top > showRect.top) {
-            scrollY += showRect.top - visibleRect.top;
-        } else if (visibleRect.bottom < showRect.bottom) {
-            scrollY += showRect.bottom - visibleRect.bottom;
-        }
-
-        contentScrollTo(scrollX, scrollY, false);
-    }
-
-    @Override
-    public void onScrollChanged(int l, int t, int oldl, int oldt) {
-        if (!mInOverScrollMode) {
-            sendOurVisibleRect();
-            // update WebKit if visible title bar height changed. The logic is same
-            // as getVisibleTitleHeightImpl.
-            int titleHeight = getTitleHeight();
-            if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
-                sendViewSizeZoom(false);
-            }
-        }
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        switch (event.getAction()) {
-            case KeyEvent.ACTION_DOWN:
-                mKeysPressed.add(Integer.valueOf(event.getKeyCode()));
-                break;
-            case KeyEvent.ACTION_MULTIPLE:
-                // Always accept the action.
-                break;
-            case KeyEvent.ACTION_UP:
-                int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode()));
-                if (location == -1) {
-                    // We did not receive the key down for this key, so do not
-                    // handle the key up.
-                    return false;
-                } else {
-                    // We did receive the key down.  Handle the key up, and
-                    // remove it from our pressed keys.
-                    mKeysPressed.remove(location);
-                }
-                break;
-            default:
-                // Accept the action.  This should not happen, unless a new
-                // action is added to KeyEvent.
-                break;
-        }
-        return mWebViewPrivate.super_dispatchKeyEvent(event);
-    }
-    
-    private static final int SNAP_BOUND = 16;
-    private static int sChannelDistance = 16;
-    private int mFirstTouchX = -1; // the first touched point
-    private int mFirstTouchY = -1;
-    private int mDistanceX = 0;
-    private int mDistanceY = 0;
-
-    private boolean inFullScreenMode() {
-        return mFullScreenHolder != null;
-    }
-
-    private void dismissFullScreenMode() {
-        if (inFullScreenMode()) {
-            mFullScreenHolder.hide();
-            mFullScreenHolder = null;
-            invalidate();
-        }
-    }
-
-    void onPinchToZoomAnimationStart() {
-        // cancel the single touch handling
-        cancelTouch();
-        onZoomAnimationStart();
-    }
-
-    void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
-        onZoomAnimationEnd();
-        // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
-        // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
-        // as it may trigger the unwanted fling.
-        mTouchMode = TOUCH_PINCH_DRAG;
-        mConfirmMove = true;
-        startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
-    }
-
-    // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a
-    // layer is found.
-    private void startScrollingLayer(float x, float y) {
-        if (mNativeClass == 0)
-            return;
-
-        int contentX = viewToContentX((int) x + getScrollX());
-        int contentY = viewToContentY((int) y + getScrollY());
-        mCurrentScrollingLayerId = nativeScrollableLayer(mNativeClass,
-                contentX, contentY, mScrollingLayerRect, mScrollingLayerBounds);
-        if (mCurrentScrollingLayerId != 0) {
-            mTouchMode = TOUCH_DRAG_LAYER_MODE;
-        }
-    }
-
-    // 1/(density * density) used to compute the distance between points.
-    // Computed in init().
-    private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
-
-    // The distance between two points reported in onTouchEvent scaled by the
-    // density of the screen.
-    private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
-
-    @Override
-    public boolean onHoverEvent(MotionEvent event) {
-        if (mNativeClass == 0) {
-            return false;
-        }
-        int x = viewToContentX((int) event.getX() + getScrollX());
-        int y = viewToContentY((int) event.getY() + getScrollY());
-        mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, x, y);
-        mWebViewPrivate.super_onHoverEvent(event);
-        return true;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (mNativeClass == 0 || (!mWebView.isClickable() && !mWebView.isLongClickable())) {
-            return false;
-        }
-
-        if (mInputDispatcher == null) {
-            return false;
-        }
-
-        if (mWebView.isFocusable() && mWebView.isFocusableInTouchMode()
-                && !mWebView.isFocused()) {
-            mWebView.requestFocus();
-        }
-
-        if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
-                getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
-            mInputDispatcher.dispatchUiEvents();
-            return true;
-        } else {
-            Log.w(LOGTAG, "mInputDispatcher rejected the event!");
-            return false;
-        }
-    }
-
-    /*
-    * Common code for single touch and multi-touch.
-    * (x, y) denotes current focus point, which is the touch point for single touch
-    * and the middle point for multi-touch.
-    */
-    private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
-        ScaleGestureDetector detector = mZoomManager.getScaleGestureDetector();
-
-        long eventTime = event.getEventTime();
-
-        // Due to the touch screen edge effect, a touch closer to the edge
-        // always snapped to the edge. As getViewWidth() can be different from
-        // getWidth() due to the scrollbar, adjusting the point to match
-        // getViewWidth(). Same applied to the height.
-        x = Math.min(x, getViewWidth() - 1);
-        y = Math.min(y, getViewHeightWithTitle() - 1);
-
-        int deltaX = mLastTouchX - x;
-        int deltaY = mLastTouchY - y;
-        int contentX = viewToContentX(x + getScrollX());
-        int contentY = viewToContentY(y + getScrollY());
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN: {
-                mConfirmMove = false;
-
-                // Channel Scrolling
-                mFirstTouchX = x;
-                mFirstTouchY = y;
-                mDistanceX = mDistanceY = 0;
-
-                if (!mEditTextScroller.isFinished()) {
-                    mEditTextScroller.abortAnimation();
-                }
-                if (!mScroller.isFinished()) {
-                    // stop the current scroll animation, but if this is
-                    // the start of a fling, allow it to add to the current
-                    // fling's velocity
-                    mScroller.abortAnimation();
-                    mTouchMode = TOUCH_DRAG_START_MODE;
-                    mConfirmMove = true;
-                    nativeSetIsScrolling(false);
-                } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
-                    mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
-                    removeTouchHighlight();
-                    if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
-                        mTouchMode = TOUCH_DOUBLE_TAP_MODE;
-                    } else {
-                        mTouchMode = TOUCH_INIT_MODE;
-                    }
-                } else { // the normal case
-                    mTouchMode = TOUCH_INIT_MODE;
-                    if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
-                        EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
-                                (eventTime - mLastTouchUpTime), eventTime);
-                    }
-                    mSelectionStarted = false;
-                    if (mSelectingText) {
-                        ensureSelectionHandles();
-                        int shiftedY = y - getTitleHeight() + getScrollY();
-                        int shiftedX = x + getScrollX();
-                        if (mSelectHandleBaseBounds.contains(shiftedX, shiftedY)) {
-                            mSelectionStarted = true;
-                            mSelectDraggingCursor = mSelectCursorBase;
-                            mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
-                            if (mIsCaretSelection) {
-                                mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
-                                hidePasteButton();
-                            }
-                        } else if (mSelectHandleExtentBounds
-                                .contains(shiftedX, shiftedY)) {
-                            mSelectionStarted = true;
-                            mSelectDraggingCursor = mSelectCursorExtent;
-                            mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
-                        } else if (mIsCaretSelection) {
-                            selectionDone();
-                        }
-                        if (DebugFlags.WEB_VIEW) {
-                            Log.v(LOGTAG, "select=" + contentX + "," + contentY);
-                        }
-                    }
-                }
-                // Trigger the link
-                if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE
-                        || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) {
-                    mPrivateHandler.sendEmptyMessageDelayed(
-                            SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
-                    mPrivateHandler.sendEmptyMessageDelayed(
-                            SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
-                }
-                startTouch(x, y, eventTime);
-                if (mIsEditingText) {
-                    mTouchInEditText = mEditTextContentBounds
-                            .contains(contentX, contentY);
-                }
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
-                        >= mTouchSlopSquare) {
-                    mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-                    mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                    mConfirmMove = true;
-                    if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
-                        mTouchMode = TOUCH_INIT_MODE;
-                    }
-                    removeTouchHighlight();
-                }
-                if (mSelectingText && mSelectionStarted) {
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
-                    }
-                    ViewParent parent = mWebView.getParent();
-                    if (parent != null) {
-                        parent.requestDisallowInterceptTouchEvent(true);
-                    }
-                    if (deltaX != 0 || deltaY != 0) {
-                        int handleX = contentX +
-                                viewToContentDimension(mSelectOffset.x);
-                        int handleY = contentY +
-                                viewToContentDimension(mSelectOffset.y);
-                        mSelectDraggingCursor.set(handleX, handleY);
-                        boolean inCursorText =
-                                mSelectDraggingTextQuad.containsPoint(handleX, handleY);
-                        boolean inEditBounds = mEditTextContentBounds
-                                .contains(handleX, handleY);
-                        if (mIsEditingText && !inEditBounds) {
-                            beginScrollEdit();
-                        } else {
-                            endScrollEdit();
-                        }
-                        boolean snapped = false;
-                        if (inCursorText || (mIsEditingText && !inEditBounds)) {
-                            snapDraggingCursor();
-                            snapped = true;
-                        }
-                        updateWebkitSelection(snapped);
-                        if (!inCursorText && mIsEditingText && inEditBounds) {
-                            // Visually snap even if we have moved the handle.
-                            snapDraggingCursor();
-                        }
-                        mLastTouchX = x;
-                        mLastTouchY = y;
-                        invalidate();
-                    }
-                    break;
-                }
-
-                if (mTouchMode == TOUCH_DONE_MODE) {
-                    // no dragging during scroll zoom animation, or when prevent
-                    // default is yes
-                    break;
-                }
-                if (mVelocityTracker == null) {
-                    Log.e(LOGTAG, "Got null mVelocityTracker when "
-                            + " mTouchMode = " + mTouchMode);
-                } else {
-                    mVelocityTracker.addMovement(event);
-                }
-
-                if (mTouchMode != TOUCH_DRAG_MODE &&
-                        mTouchMode != TOUCH_DRAG_LAYER_MODE &&
-                        mTouchMode != TOUCH_DRAG_TEXT_MODE) {
-
-                    if (!mConfirmMove) {
-                        break;
-                    }
-
-                    if ((detector == null || !detector.isInProgress())
-                            && SNAP_NONE == mSnapScrollMode) {
-                        int ax = Math.abs(x - mFirstTouchX);
-                        int ay = Math.abs(y - mFirstTouchY);
-                        if (ax < SNAP_BOUND && ay < SNAP_BOUND) {
-                            break;
-                        } else if (ax < SNAP_BOUND) {
-                            mSnapScrollMode = SNAP_Y;
-                        } else if (ay < SNAP_BOUND) {
-                            mSnapScrollMode = SNAP_X;
-                        }
-                    }
-
-                    mTouchMode = TOUCH_DRAG_MODE;
-                    mLastTouchX = x;
-                    mLastTouchY = y;
-                    deltaX = 0;
-                    deltaY = 0;
-
-                    startScrollingLayer(x, y);
-                    startDrag();
-                }
-
-                // do pan
-                boolean keepScrollBarsVisible = false;
-                if (deltaX == 0 && deltaY == 0) {
-                    keepScrollBarsVisible = true;
-                } else {
-                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
-                        mDistanceX += Math.abs(deltaX);
-                        mDistanceY += Math.abs(deltaY);
-                        if (mSnapScrollMode == SNAP_X) {
-                            if (mDistanceY > sChannelDistance) {
-                                mSnapScrollMode = SNAP_NONE;
-                            } else if (mDistanceX > sChannelDistance) {
-                                mDistanceX = mDistanceY = 0;
-                        }
-                    } else {
-                            if (mDistanceX > sChannelDistance) {
-                                mSnapScrollMode = SNAP_NONE;
-                            } else if (mDistanceY > sChannelDistance) {
-                                mDistanceX = mDistanceY = 0;
-                            }
-                        }
-                    }
-                    if (mSnapScrollMode != SNAP_NONE) {
-                        if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
-                            deltaY = 0;
-                        } else {
-                            deltaX = 0;
-                        }
-                    }
-                    if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
-                        mHeldMotionless = MOTIONLESS_FALSE;
-                    } else {
-                        mHeldMotionless = MOTIONLESS_TRUE;
-                        keepScrollBarsVisible = true;
-                    }
-
-                    mLastTouchTime = eventTime;
-                    boolean allDrag = doDrag(deltaX, deltaY);
-                    if (allDrag) {
-                        mLastTouchX = x;
-                        mLastTouchY = y;
-                    } else {
-                        int contentDeltaX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
-                        int roundedDeltaX = contentToViewDimension(contentDeltaX);
-                        int contentDeltaY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
-                        int roundedDeltaY = contentToViewDimension(contentDeltaY);
-                        mLastTouchX -= roundedDeltaX;
-                        mLastTouchY -= roundedDeltaY;
-                    }
-                }
-
-                break;
-            }
-            case MotionEvent.ACTION_UP: {
-                mFirstTouchX  = mFirstTouchY = -1;
-                if (mIsEditingText && mSelectionStarted) {
-                    endScrollEdit();
-                    mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW,
-                            TEXT_SCROLL_FIRST_SCROLL_MS);
-                    if (!mConfirmMove && mIsCaretSelection) {
-                        showPasteWindow();
-                        stopTouch();
-                        break;
-                    }
-                }
-                mLastTouchUpTime = eventTime;
-                if (mSentAutoScrollMessage) {
-                    mAutoScrollX = mAutoScrollY = 0;
-                }
-                switch (mTouchMode) {
-                    case TOUCH_DOUBLE_TAP_MODE: // double tap
-                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                        mTouchMode = TOUCH_DONE_MODE;
-                        break;
-                    case TOUCH_INIT_MODE: // tap
-                    case TOUCH_SHORTPRESS_START_MODE:
-                    case TOUCH_SHORTPRESS_MODE:
-                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-                        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                        if (!mConfirmMove) {
-                            if (mSelectingText) {
-                                // tapping on selection or controls does nothing
-                                if (!mSelectionStarted) {
-                                    selectionDone();
-                                }
-                                break;
-                            }
-                            // only trigger double tap if the WebView is
-                            // scalable
-                            if (mTouchMode == TOUCH_INIT_MODE
-                                    && (canZoomIn() || canZoomOut())) {
-                                mPrivateHandler.sendEmptyMessageDelayed(
-                                        RELEASE_SINGLE_TAP, ViewConfiguration
-                                                .getDoubleTapTimeout());
-                            }
-                            break;
-                        }
-                    case TOUCH_DRAG_MODE:
-                    case TOUCH_DRAG_LAYER_MODE:
-                    case TOUCH_DRAG_TEXT_MODE:
-                        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
-                        // if the user waits a while w/o moving before the
-                        // up, we don't want to do a fling
-                        if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
-                            if (mVelocityTracker == null) {
-                                Log.e(LOGTAG, "Got null mVelocityTracker");
-                            } else {
-                                mVelocityTracker.addMovement(event);
-                            }
-                            // set to MOTIONLESS_IGNORE so that it won't keep
-                            // removing and sending message in
-                            // drawCoreAndCursorRing()
-                            mHeldMotionless = MOTIONLESS_IGNORE;
-                            doFling();
-                            break;
-                        } else {
-                            if (mScroller.springBack(getScrollX(), getScrollY(), 0,
-                                    computeMaxScrollX(), 0,
-                                    computeMaxScrollY())) {
-                                invalidate();
-                            }
-                        }
-                        // redraw in high-quality, as we're done dragging
-                        mHeldMotionless = MOTIONLESS_TRUE;
-                        invalidate();
-                        // fall through
-                    case TOUCH_DRAG_START_MODE:
-                        // TOUCH_DRAG_START_MODE should not happen for the real
-                        // device as we almost certain will get a MOVE. But this
-                        // is possible on emulator.
-                        mLastVelocity = 0;
-                        WebViewCore.resumePriority();
-                        if (!mSelectingText) {
-                            WebViewCore.resumeUpdatePicture(mWebViewCore);
-                        }
-                        break;
-                }
-                stopTouch();
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL: {
-                if (mTouchMode == TOUCH_DRAG_MODE) {
-                    mScroller.springBack(getScrollX(), getScrollY(), 0,
-                            computeMaxScrollX(), 0, computeMaxScrollY());
-                    invalidate();
-                }
-                cancelTouch();
-                break;
-            }
-        }
-    }
-
-    /**
-     * Returns the text scroll speed in content pixels per millisecond based on
-     * the touch location.
-     * @param coordinate The x or y touch coordinate in content space
-     * @param min The minimum coordinate (x or y) of the edit content bounds
-     * @param max The maximum coordinate (x or y) of the edit content bounds
-     */
-    private static float getTextScrollSpeed(int coordinate, int min, int max) {
-        if (coordinate < min) {
-            return (coordinate - min) * TEXT_SCROLL_RATE;
-        } else if (coordinate >= max) {
-            return (coordinate - max + 1) * TEXT_SCROLL_RATE;
-        } else {
-            return 0.0f;
-        }
-    }
-
-    private static int getSelectionCoordinate(int coordinate, int min, int max) {
-        return Math.max(Math.min(coordinate, max), min);
-    }
-
-    private void beginScrollEdit() {
-        if (mLastEditScroll == 0) {
-            mLastEditScroll = SystemClock.uptimeMillis() -
-                    TEXT_SCROLL_FIRST_SCROLL_MS;
-            scrollEditWithCursor();
-        }
-    }
-
-    private void scrollDraggedSelectionHandleIntoView() {
-        if (mSelectDraggingCursor == null) {
-            return;
-        }
-        int x = mSelectDraggingCursor.x;
-        int y = mSelectDraggingCursor.y;
-        if (!mEditTextContentBounds.contains(x,y)) {
-            int left = Math.min(0, x - mEditTextContentBounds.left - EDIT_RECT_BUFFER);
-            int right = Math.max(0, x - mEditTextContentBounds.right + EDIT_RECT_BUFFER);
-            int deltaX = left + right;
-            int above = Math.min(0, y - mEditTextContentBounds.top - EDIT_RECT_BUFFER);
-            int below = Math.max(0, y - mEditTextContentBounds.bottom + EDIT_RECT_BUFFER);
-            int deltaY = above + below;
-            if (deltaX != 0 || deltaY != 0) {
-                int scrollX = getTextScrollX() + deltaX;
-                int scrollY = getTextScrollY() + deltaY;
-                scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
-                scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
-                scrollEditText(scrollX, scrollY);
-            }
-        }
-    }
-
-    private void endScrollEdit() {
-        mLastEditScroll = 0;
-    }
-
-    private static int clampBetween(int value, int min, int max) {
-        return Math.max(min, Math.min(value, max));
-    }
-
-    private static int getTextScrollDelta(float speed, long deltaT) {
-        float distance = speed * deltaT;
-        int intDistance = (int)Math.floor(distance);
-        float probability = distance - intDistance;
-        if (Math.random() < probability) {
-            intDistance++;
-        }
-        return intDistance;
-    }
-    /**
-     * Scrolls edit text a distance based on the last touch point,
-     * the last scroll time, and the edit text content bounds.
-     */
-    private void scrollEditWithCursor() {
-        if (mLastEditScroll != 0) {
-            int x = viewToContentX(mLastTouchX + getScrollX() + mSelectOffset.x);
-            float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
-                    mEditTextContentBounds.right);
-            int y = viewToContentY(mLastTouchY + getScrollY() + mSelectOffset.y);
-            float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
-                    mEditTextContentBounds.bottom);
-            if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
-                endScrollEdit();
-            } else {
-                long currentTime = SystemClock.uptimeMillis();
-                long timeSinceLastUpdate = currentTime - mLastEditScroll;
-                int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
-                int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
-                int scrollX = getTextScrollX() + deltaX;
-                scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
-                int scrollY = getTextScrollY() + deltaY;
-                scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
-
-                mLastEditScroll = currentTime;
-                if (scrollX == getTextScrollX() && scrollY == getTextScrollY()) {
-                    // By probability no text scroll this time. Try again later.
-                    mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
-                            TEXT_SCROLL_FIRST_SCROLL_MS);
-                } else {
-                    int selectionX = getSelectionCoordinate(x,
-                            mEditTextContentBounds.left, mEditTextContentBounds.right);
-                    int selectionY = getSelectionCoordinate(y,
-                            mEditTextContentBounds.top, mEditTextContentBounds.bottom);
-                    int oldX = mSelectDraggingCursor.x;
-                    int oldY = mSelectDraggingCursor.y;
-                    mSelectDraggingCursor.set(selectionX, selectionY);
-                    updateWebkitSelection(false);
-                    scrollEditText(scrollX, scrollY);
-                    mSelectDraggingCursor.set(oldX, oldY);
-                }
-            }
-        }
-    }
-
-    private void startTouch(float x, float y, long eventTime) {
-        // Remember where the motion event started
-        mStartTouchX = mLastTouchX = Math.round(x);
-        mStartTouchY = mLastTouchY = Math.round(y);
-        mLastTouchTime = eventTime;
-        mVelocityTracker = VelocityTracker.obtain();
-        mSnapScrollMode = SNAP_NONE;
-    }
-
-    private void startDrag() {
-        WebViewCore.reducePriority();
-        // to get better performance, pause updating the picture
-        WebViewCore.pauseUpdatePicture(mWebViewCore);
-        nativeSetIsScrolling(true);
-
-        if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
-                || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
-            mZoomManager.invokeZoomPicker();
-        }
-    }
-
-    private boolean doDrag(int deltaX, int deltaY) {
-        boolean allDrag = true;
-        if ((deltaX | deltaY) != 0) {
-            int oldX = getScrollX();
-            int oldY = getScrollY();
-            int rangeX = computeMaxScrollX();
-            int rangeY = computeMaxScrollY();
-            final int contentX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
-            final int contentY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
-
-            // Assume page scrolling and change below if we're wrong
-            mTouchMode = TOUCH_DRAG_MODE;
-
-            // Check special scrolling before going to main page scrolling.
-            if (mIsEditingText && mTouchInEditText && canTextScroll(deltaX, deltaY)) {
-                // Edit text scrolling
-                oldX = getTextScrollX();
-                rangeX = getMaxTextScrollX();
-                deltaX = contentX;
-                oldY = getTextScrollY();
-                rangeY = getMaxTextScrollY();
-                deltaY = contentY;
-                mTouchMode = TOUCH_DRAG_TEXT_MODE;
-                allDrag = false;
-            } else if (mCurrentScrollingLayerId != 0) {
-                // Check the scrolling bounds to see if we will actually do any
-                // scrolling.  The rectangle is in document coordinates.
-                final int maxX = mScrollingLayerRect.right;
-                final int maxY = mScrollingLayerRect.bottom;
-                final int resultX = clampBetween(maxX, 0,
-                        mScrollingLayerRect.left + contentX);
-                final int resultY = clampBetween(maxY, 0,
-                        mScrollingLayerRect.top + contentY);
-
-                if (resultX != mScrollingLayerRect.left
-                        || resultY != mScrollingLayerRect.top
-                        || (contentX | contentY) == 0) {
-                    // In case we switched to dragging the page.
-                    mTouchMode = TOUCH_DRAG_LAYER_MODE;
-                    deltaX = contentX;
-                    deltaY = contentY;
-                    oldX = mScrollingLayerRect.left;
-                    oldY = mScrollingLayerRect.top;
-                    rangeX = maxX;
-                    rangeY = maxY;
-                    allDrag = false;
-                }
-            }
-
-            if (mOverScrollGlow != null) {
-                mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
-            }
-
-            mWebViewPrivate.overScrollBy(deltaX, deltaY, oldX, oldY,
-                    rangeX, rangeY,
-                    mOverscrollDistance, mOverscrollDistance, true);
-            if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
-                invalidate();
-            }
-        }
-        mZoomManager.keepZoomPickerVisible();
-        return allDrag;
-    }
-
-    private void stopTouch() {
-        if (mScroller.isFinished() && !mSelectingText
-                && (mTouchMode == TOUCH_DRAG_MODE
-                || mTouchMode == TOUCH_DRAG_LAYER_MODE)) {
-            WebViewCore.resumePriority();
-            WebViewCore.resumeUpdatePicture(mWebViewCore);
-            nativeSetIsScrolling(false);
-        }
-
-        // we also use mVelocityTracker == null to tell us that we are
-        // not "moving around", so we can take the slower/prettier
-        // mode in the drawing code
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-
-        // Release any pulled glows
-        if (mOverScrollGlow != null) {
-            mOverScrollGlow.releaseAll();
-        }
-
-        if (mSelectingText) {
-            mSelectionStarted = false;
-            syncSelectionCursors();
-            if (mIsCaretSelection) {
-                resetCaretTimer();
-            }
-            invalidate();
-        }
-    }
-
-    private void cancelTouch() {
-        // we also use mVelocityTracker == null to tell us that we are
-        // not "moving around", so we can take the slower/prettier
-        // mode in the drawing code
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-
-        if ((mTouchMode == TOUCH_DRAG_MODE
-                || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) {
-            WebViewCore.resumePriority();
-            WebViewCore.resumeUpdatePicture(mWebViewCore);
-            nativeSetIsScrolling(false);
-        }
-        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
-        removeTouchHighlight();
-        mHeldMotionless = MOTIONLESS_TRUE;
-        mTouchMode = TOUCH_DONE_MODE;
-    }
-
-    private void snapDraggingCursor() {
-        float scale = scaleAlongSegment(
-                mSelectDraggingCursor.x, mSelectDraggingCursor.y,
-                mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
-        // clamp scale to ensure point is on the bottom segment
-        scale = Math.max(0.0f, scale);
-        scale = Math.min(scale, 1.0f);
-        float newX = scaleCoordinate(scale,
-                mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
-        float newY = scaleCoordinate(scale,
-                mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
-        int x = Math.round(newX);
-        int y = Math.round(newY);
-        if (mIsEditingText) {
-            x = clampBetween(x, mEditTextContentBounds.left,
-                    mEditTextContentBounds.right);
-            y = clampBetween(y, mEditTextContentBounds.top,
-                    mEditTextContentBounds.bottom);
-        }
-        mSelectDraggingCursor.set(x, y);
-    }
-
-    private static float scaleCoordinate(float scale, float coord1, float coord2) {
-        float diff = coord2 - coord1;
-        return coord1 + (scale * diff);
-    }
-
-    @Override
-    public boolean onGenericMotionEvent(MotionEvent event) {
-        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_SCROLL: {
-                    final float vscroll;
-                    final float hscroll;
-                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
-                        vscroll = 0;
-                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
-                    } else {
-                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
-                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
-                    }
-                    if (hscroll != 0 || vscroll != 0) {
-                        final int vdelta = (int) (vscroll *
-                                mWebViewPrivate.getVerticalScrollFactor());
-                        final int hdelta = (int) (hscroll *
-                                mWebViewPrivate.getHorizontalScrollFactor());
-
-                        abortAnimation();
-                        int oldTouchMode = mTouchMode;
-                        startScrollingLayer(event.getX(), event.getY());
-                        doDrag(hdelta, vdelta);
-                        mTouchMode = oldTouchMode;
-                        return true;
-                    }
-                }
-            }
-        }
-        return mWebViewPrivate.super_onGenericMotionEvent(event);
-    }
-
-    private long mTrackballFirstTime = 0;
-    private long mTrackballLastTime = 0;
-    private float mTrackballRemainsX = 0.0f;
-    private float mTrackballRemainsY = 0.0f;
-    private int mTrackballXMove = 0;
-    private int mTrackballYMove = 0;
-    private boolean mSelectingText = false;
-    private boolean mShowTextSelectionExtra = false;
-    private boolean mSelectionStarted = false;
-    private static final int TRACKBALL_KEY_TIMEOUT = 1000;
-    private static final int TRACKBALL_TIMEOUT = 200;
-    private static final int TRACKBALL_WAIT = 100;
-    private static final int TRACKBALL_SCALE = 400;
-    private static final int TRACKBALL_SCROLL_COUNT = 5;
-    private static final int TRACKBALL_MOVE_COUNT = 10;
-    private static final int TRACKBALL_MULTIPLIER = 3;
-    private static final int SELECT_CURSOR_OFFSET = 16;
-    private static final int SELECT_SCROLL = 5;
-    private int mSelectX = 0;
-    private int mSelectY = 0;
-    private boolean mTrackballDown = false;
-    private long mTrackballUpTime = 0;
-    private long mLastCursorTime = 0;
-    private Rect mLastCursorBounds;
-    private SelectionHandleAlpha mBaseAlpha = new SelectionHandleAlpha();
-    private SelectionHandleAlpha mExtentAlpha = new SelectionHandleAlpha();
-    private ObjectAnimator mBaseHandleAlphaAnimator =
-            ObjectAnimator.ofInt(mBaseAlpha, "alpha", 0);
-    private ObjectAnimator mExtentHandleAlphaAnimator =
-            ObjectAnimator.ofInt(mExtentAlpha, "alpha", 0);
-
-    // Set by default; BrowserActivity clears to interpret trackball data
-    // directly for movement. Currently, the framework only passes
-    // arrow key events, not trackball events, from one child to the next
-    private boolean mMapTrackballToArrowKeys = true;
-
-    private DrawData mDelaySetPicture;
-    private DrawData mLoadedPicture;
-
-    @Override
-    public void setMapTrackballToArrowKeys(boolean setMap) {
-        mMapTrackballToArrowKeys = setMap;
-    }
-
-    void resetTrackballTime() {
-        mTrackballLastTime = 0;
-    }
-
-    @Override
-    public boolean onTrackballEvent(MotionEvent ev) {
-        long time = ev.getEventTime();
-        if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
-            if (ev.getY() > 0) pageDown(true);
-            if (ev.getY() < 0) pageUp(true);
-            return true;
-        }
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if (mSelectingText) {
-                return true; // discard press if copy in progress
-            }
-            mTrackballDown = true;
-            if (mNativeClass == 0) {
-                return false;
-            }
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
-                        + " time=" + time
-                        + " mLastCursorTime=" + mLastCursorTime);
-            }
-            if (mWebView.isInTouchMode()) mWebView.requestFocusFromTouch();
-            return false; // let common code in onKeyDown at it
-        }
-        if (ev.getAction() == MotionEvent.ACTION_UP) {
-            // LONG_PRESS_CENTER is set in common onKeyDown
-            mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
-            mTrackballDown = false;
-            mTrackballUpTime = time;
-            if (mSelectingText) {
-                copySelection();
-                selectionDone();
-                return true; // discard press if copy in progress
-            }
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
-                        + " time=" + time
-                );
-            }
-            return false; // let common code in onKeyUp at it
-        }
-        if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) ||
-                AccessibilityManager.getInstance(mContext).isEnabled()) {
-            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
-            return false;
-        }
-        if (mTrackballDown) {
-            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
-            return true; // discard move if trackball is down
-        }
-        if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
-            if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
-            return true;
-        }
-        // TODO: alternatively we can do panning as touch does
-        switchOutDrawHistory();
-        if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "onTrackballEvent time="
-                        + time + " last=" + mTrackballLastTime);
-            }
-            mTrackballFirstTime = time;
-            mTrackballXMove = mTrackballYMove = 0;
-        }
-        mTrackballLastTime = time;
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
-        }
-        mTrackballRemainsX += ev.getX();
-        mTrackballRemainsY += ev.getY();
-        doTrackball(time, ev.getMetaState());
-        return true;
-    }
-
-    private int scaleTrackballX(float xRate, int width) {
-        int xMove = (int) (xRate / TRACKBALL_SCALE * width);
-        int nextXMove = xMove;
-        if (xMove > 0) {
-            if (xMove > mTrackballXMove) {
-                xMove -= mTrackballXMove;
-            }
-        } else if (xMove < mTrackballXMove) {
-            xMove -= mTrackballXMove;
-        }
-        mTrackballXMove = nextXMove;
-        return xMove;
-    }
-
-    private int scaleTrackballY(float yRate, int height) {
-        int yMove = (int) (yRate / TRACKBALL_SCALE * height);
-        int nextYMove = yMove;
-        if (yMove > 0) {
-            if (yMove > mTrackballYMove) {
-                yMove -= mTrackballYMove;
-            }
-        } else if (yMove < mTrackballYMove) {
-            yMove -= mTrackballYMove;
-        }
-        mTrackballYMove = nextYMove;
-        return yMove;
-    }
-
-    private int keyCodeToSoundsEffect(int keyCode) {
-        switch(keyCode) {
-            case KeyEvent.KEYCODE_DPAD_UP:
-                return SoundEffectConstants.NAVIGATION_UP;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                return SoundEffectConstants.NAVIGATION_RIGHT;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                return SoundEffectConstants.NAVIGATION_DOWN;
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                return SoundEffectConstants.NAVIGATION_LEFT;
-        }
-        return 0;
-    }
-
-    private void doTrackball(long time, int metaState) {
-        int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
-        if (elapsed == 0) {
-            elapsed = TRACKBALL_TIMEOUT;
-        }
-        float xRate = mTrackballRemainsX * 1000 / elapsed;
-        float yRate = mTrackballRemainsY * 1000 / elapsed;
-        int viewWidth = getViewWidth();
-        int viewHeight = getViewHeight();
-        float ax = Math.abs(xRate);
-        float ay = Math.abs(yRate);
-        float maxA = Math.max(ax, ay);
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
-                    + " xRate=" + xRate
-                    + " yRate=" + yRate
-                    + " mTrackballRemainsX=" + mTrackballRemainsX
-                    + " mTrackballRemainsY=" + mTrackballRemainsY);
-        }
-        int width = mContentWidth - viewWidth;
-        int height = mContentHeight - viewHeight;
-        if (width < 0) width = 0;
-        if (height < 0) height = 0;
-        ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
-        ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
-        maxA = Math.max(ax, ay);
-        int count = Math.max(0, (int) maxA);
-        int oldScrollX = getScrollX();
-        int oldScrollY = getScrollY();
-        if (count > 0) {
-            int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
-                    KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
-                    mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
-                    KeyEvent.KEYCODE_DPAD_RIGHT;
-            count = Math.min(count, TRACKBALL_MOVE_COUNT);
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
-                        + " count=" + count
-                        + " mTrackballRemainsX=" + mTrackballRemainsX
-                        + " mTrackballRemainsY=" + mTrackballRemainsY);
-            }
-            if (mNativeClass != 0) {
-                for (int i = 0; i < count; i++) {
-                    letPageHandleNavKey(selectKeyCode, time, true, metaState);
-                }
-                letPageHandleNavKey(selectKeyCode, time, false, metaState);
-            }
-            mTrackballRemainsX = mTrackballRemainsY = 0;
-        }
-        if (count >= TRACKBALL_SCROLL_COUNT) {
-            int xMove = scaleTrackballX(xRate, width);
-            int yMove = scaleTrackballY(yRate, height);
-            if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "doTrackball pinScrollBy"
-                        + " count=" + count
-                        + " xMove=" + xMove + " yMove=" + yMove
-                        + " mScrollX-oldScrollX=" + (getScrollX()-oldScrollX)
-                        + " mScrollY-oldScrollY=" + (getScrollY()-oldScrollY)
-                        );
-            }
-            if (Math.abs(getScrollX() - oldScrollX) > Math.abs(xMove)) {
-                xMove = 0;
-            }
-            if (Math.abs(getScrollY() - oldScrollY) > Math.abs(yMove)) {
-                yMove = 0;
-            }
-            if (xMove != 0 || yMove != 0) {
-                pinScrollBy(xMove, yMove, true, 0);
-            }
-        }
-    }
-
-    /**
-     * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
-     * @return Maximum horizontal scroll position within real content
-     */
-    int computeMaxScrollX() {
-        return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
-    }
-
-    /**
-     * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
-     * @return Maximum vertical scroll position within real content
-     */
-    int computeMaxScrollY() {
-        return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
-                - getViewHeightWithTitle(), 0);
-    }
-
-    boolean updateScrollCoordinates(int x, int y) {
-        int oldX = getScrollX();
-        int oldY = getScrollY();
-        setScrollXRaw(x);
-        setScrollYRaw(y);
-        if (oldX != getScrollX() || oldY != getScrollY()) {
-            mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public void flingScroll(int vx, int vy) {
-        mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
-                computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
-        invalidate();
-    }
-
-    private void doFling() {
-        if (mVelocityTracker == null) {
-            return;
-        }
-        int maxX = computeMaxScrollX();
-        int maxY = computeMaxScrollY();
-
-        mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
-        int vx = (int) mVelocityTracker.getXVelocity();
-        int vy = (int) mVelocityTracker.getYVelocity();
-
-        int scrollX = getScrollX();
-        int scrollY = getScrollY();
-        int overscrollDistance = mOverscrollDistance;
-        int overflingDistance = mOverflingDistance;
-
-        // Use the layer's scroll data if applicable.
-        if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
-            scrollX = mScrollingLayerRect.left;
-            scrollY = mScrollingLayerRect.top;
-            maxX = mScrollingLayerRect.right;
-            maxY = mScrollingLayerRect.bottom;
-            // No overscrolling for layers.
-            overscrollDistance = overflingDistance = 0;
-        } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
-            scrollX = getTextScrollX();
-            scrollY = getTextScrollY();
-            maxX = getMaxTextScrollX();
-            maxY = getMaxTextScrollY();
-            // No overscrolling for edit text.
-            overscrollDistance = overflingDistance = 0;
-        }
-
-        if (mSnapScrollMode != SNAP_NONE) {
-            if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
-                vy = 0;
-            } else {
-                vx = 0;
-            }
-        }
-        if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
-            WebViewCore.resumePriority();
-            if (!mSelectingText) {
-                WebViewCore.resumeUpdatePicture(mWebViewCore);
-            }
-            if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) {
-                invalidate();
-            }
-            return;
-        }
-        float currentVelocity = mScroller.getCurrVelocity();
-        float velocity = (float) Math.hypot(vx, vy);
-        if (mLastVelocity > 0 && currentVelocity > 0 && velocity
-                > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
-            float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
-                    - Math.atan2(vy, vx)));
-            final float circle = (float) (Math.PI) * 2.0f;
-            if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
-                vx += currentVelocity * mLastVelX / mLastVelocity;
-                vy += currentVelocity * mLastVelY / mLastVelocity;
-                velocity = (float) Math.hypot(vx, vy);
-                if (DebugFlags.WEB_VIEW) {
-                    Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
-                }
-            } else if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, "doFling missed " + deltaR / circle);
-            }
-        } else if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "doFling start last=" + mLastVelocity
-                    + " current=" + currentVelocity
-                    + " vx=" + vx + " vy=" + vy
-                    + " maxX=" + maxX + " maxY=" + maxY
-                    + " scrollX=" + scrollX + " scrollY=" + scrollY
-                    + " layer=" + mCurrentScrollingLayerId);
-        }
-
-        // Allow sloppy flings without overscrolling at the edges.
-        if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
-            vx = 0;
-        }
-        if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
-            vy = 0;
-        }
-
-        if (overscrollDistance < overflingDistance) {
-            if ((vx > 0 && scrollX == -overscrollDistance) ||
-                    (vx < 0 && scrollX == maxX + overscrollDistance)) {
-                vx = 0;
-            }
-            if ((vy > 0 && scrollY == -overscrollDistance) ||
-                    (vy < 0 && scrollY == maxY + overscrollDistance)) {
-                vy = 0;
-            }
-        }
-
-        mLastVelX = vx;
-        mLastVelY = vy;
-        mLastVelocity = velocity;
-
-        // no horizontal overscroll if the content just fits
-        mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY,
-                maxX == 0 ? 0 : overflingDistance, overflingDistance);
-
-        invalidate();
-    }
-
-    /**
-     * See {@link WebView#getZoomControls()}
-     */
-    @Override
-    @Deprecated
-    public View getZoomControls() {
-        if (!getSettings().supportZoom()) {
-            Log.w(LOGTAG, "This WebView doesn't support zoom.");
-            return null;
-        }
-        return mZoomManager.getExternalZoomPicker();
-    }
-
-    void dismissZoomControl() {
-        mZoomManager.dismissZoomPicker();
-    }
-
-    float getDefaultZoomScale() {
-        return mZoomManager.getDefaultScale();
-    }
-
-    /**
-     * Return the overview scale of the WebView
-     * @return The overview scale.
-     */
-    float getZoomOverviewScale() {
-        return mZoomManager.getZoomOverviewScale();
-    }
-
-    /**
-     * See {@link WebView#canZoomIn()}
-     */
-    @Override
-    public boolean canZoomIn() {
-        return mZoomManager.canZoomIn();
-    }
-
-    /**
-     * See {@link WebView#canZoomOut()}
-     */
-    @Override
-    public boolean canZoomOut() {
-        return mZoomManager.canZoomOut();
-    }
-
-    /**
-     * See {@link WebView#zoomIn()}
-     */
-    @Override
-    public boolean zoomIn() {
-        return mZoomManager.zoomIn();
-    }
-
-    /**
-     * See {@link WebView#zoomOut()}
-     */
-    @Override
-    public boolean zoomOut() {
-        return mZoomManager.zoomOut();
-    }
-
-    /*
-     * Return true if the rect (e.g. plugin) is fully visible and maximized
-     * inside the WebView.
-     */
-    boolean isRectFitOnScreen(Rect rect) {
-        final int rectWidth = rect.width();
-        final int rectHeight = rect.height();
-        final int viewWidth = getViewWidth();
-        final int viewHeight = getViewHeightWithTitle();
-        float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight);
-        scale = mZoomManager.computeScaleWithLimits(scale);
-        return !mZoomManager.willScaleTriggerZoom(scale)
-                && contentToViewX(rect.left) >= getScrollX()
-                && contentToViewX(rect.right) <= getScrollX() + viewWidth
-                && contentToViewY(rect.top) >= getScrollY()
-                && contentToViewY(rect.bottom) <= getScrollY() + viewHeight;
-    }
-
-    /*
-     * Maximize and center the rectangle, specified in the document coordinate
-     * space, inside the WebView. If the zoom doesn't need to be changed, do an
-     * animated scroll to center it. If the zoom needs to be changed, find the
-     * zoom center and do a smooth zoom transition. The rect is in document
-     * coordinates
-     */
-    void centerFitRect(Rect rect) {
-        final int rectWidth = rect.width();
-        final int rectHeight = rect.height();
-        final int viewWidth = getViewWidth();
-        final int viewHeight = getViewHeightWithTitle();
-        float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight
-                / rectHeight);
-        scale = mZoomManager.computeScaleWithLimits(scale);
-        if (!mZoomManager.willScaleTriggerZoom(scale)) {
-            pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2,
-                    contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2,
-                    true, 0);
-        } else {
-            float actualScale = mZoomManager.getScale();
-            float oldScreenX = rect.left * actualScale - getScrollX();
-            float rectViewX = rect.left * scale;
-            float rectViewWidth = rectWidth * scale;
-            float newMaxWidth = mContentWidth * scale;
-            float newScreenX = (viewWidth - rectViewWidth) / 2;
-            // pin the newX to the WebView
-            if (newScreenX > rectViewX) {
-                newScreenX = rectViewX;
-            } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
-                newScreenX = viewWidth - (newMaxWidth - rectViewX);
-            }
-            float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
-                    / (scale - actualScale);
-            float oldScreenY = rect.top * actualScale + getTitleHeight()
-                    - getScrollY();
-            float rectViewY = rect.top * scale + getTitleHeight();
-            float rectViewHeight = rectHeight * scale;
-            float newMaxHeight = mContentHeight * scale + getTitleHeight();
-            float newScreenY = (viewHeight - rectViewHeight) / 2;
-            // pin the newY to the WebView
-            if (newScreenY > rectViewY) {
-                newScreenY = rectViewY;
-            } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
-                newScreenY = viewHeight - (newMaxHeight - rectViewY);
-            }
-            float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
-                    / (scale - actualScale);
-            mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
-            mZoomManager.startZoomAnimation(scale, false);
-        }
-    }
-
-    // Called by JNI to handle a touch on a node representing an email address,
-    // address, or phone number
-    private void overrideLoading(String url) {
-        mCallbackProxy.uiOverrideUrlLoading(url);
-    }
-
-    @Override
-    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
-        // Check if we are destroyed
-        if (mWebViewCore == null) return false;
-        // FIXME: If a subwindow is showing find, and the user touches the
-        // background window, it can steal focus.
-        if (mFindIsUp) return false;
-        boolean result = false;
-        result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
-        if (mWebViewCore.getSettings().getNeedInitialFocus()
-                && !mWebView.isInTouchMode()) {
-            // For cases such as GMail, where we gain focus from a direction,
-            // we want to move to the first available link.
-            // FIXME: If there are no visible links, we may not want to
-            int fakeKeyDirection = 0;
-            switch(direction) {
-                case View.FOCUS_UP:
-                    fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
-                    break;
-                case View.FOCUS_DOWN:
-                    fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
-                    break;
-                case View.FOCUS_LEFT:
-                    fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
-                    break;
-                case View.FOCUS_RIGHT:
-                    fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
-                    break;
-                default:
-                    return result;
-            }
-            mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
-        }
-        return result;
-    }
-
-    @Override
-    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-
-        int measuredHeight = heightSize;
-        int measuredWidth = widthSize;
-
-        // Grab the content size from WebViewCore.
-        int contentHeight = contentToViewDimension(mContentHeight);
-        int contentWidth = contentToViewDimension(mContentWidth);
-
-//        Log.d(LOGTAG, "------- measure " + heightMode);
-
-        if (heightMode != MeasureSpec.EXACTLY) {
-            mHeightCanMeasure = true;
-            measuredHeight = contentHeight;
-            if (heightMode == MeasureSpec.AT_MOST) {
-                // If we are larger than the AT_MOST height, then our height can
-                // no longer be measured and we should scroll internally.
-                if (measuredHeight > heightSize) {
-                    measuredHeight = heightSize;
-                    mHeightCanMeasure = false;
-                    measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
-                }
-            }
-        } else {
-            mHeightCanMeasure = false;
-        }
-        if (mNativeClass != 0) {
-            nativeSetHeightCanMeasure(mHeightCanMeasure);
-        }
-        // For the width, always use the given size unless unspecified.
-        if (widthMode == MeasureSpec.UNSPECIFIED) {
-            mWidthCanMeasure = true;
-            measuredWidth = contentWidth;
-        } else {
-            if (measuredWidth < contentWidth) {
-                measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
-            }
-            mWidthCanMeasure = false;
-        }
-
-        synchronized (this) {
-            mWebViewPrivate.setMeasuredDimension(measuredWidth, measuredHeight);
-        }
-    }
-
-    @Override
-    public boolean requestChildRectangleOnScreen(View child,
-                                                 Rect rect,
-                                                 boolean immediate) {
-        if (mNativeClass == 0) {
-            return false;
-        }
-        // don't scroll while in zoom animation. When it is done, we will adjust
-        // the necessary components
-        if (mZoomManager.isFixedLengthAnimationInProgress()) {
-            return false;
-        }
-
-        rect.offset(child.getLeft() - child.getScrollX(),
-                child.getTop() - child.getScrollY());
-
-        Rect content = new Rect(viewToContentX(getScrollX()),
-                viewToContentY(getScrollY()),
-                viewToContentX(getScrollX() + getWidth()
-                - mWebView.getVerticalScrollbarWidth()),
-                viewToContentY(getScrollY() + getViewHeightWithTitle()));
-        int screenTop = contentToViewY(content.top);
-        int screenBottom = contentToViewY(content.bottom);
-        int height = screenBottom - screenTop;
-        int scrollYDelta = 0;
-
-        if (rect.bottom > screenBottom) {
-            int oneThirdOfScreenHeight = height / 3;
-            if (rect.height() > 2 * oneThirdOfScreenHeight) {
-                // If the rectangle is too tall to fit in the bottom two thirds
-                // of the screen, place it at the top.
-                scrollYDelta = rect.top - screenTop;
-            } else {
-                // If the rectangle will still fit on screen, we want its
-                // top to be in the top third of the screen.
-                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
-            }
-        } else if (rect.top < screenTop) {
-            scrollYDelta = rect.top - screenTop;
-        }
-
-        int screenLeft = contentToViewX(content.left);
-        int screenRight = contentToViewX(content.right);
-        int width = screenRight - screenLeft;
-        int scrollXDelta = 0;
-
-        if (rect.right > screenRight && rect.left > screenLeft) {
-            if (rect.width() > width) {
-                scrollXDelta += (rect.left - screenLeft);
-            } else {
-                scrollXDelta += (rect.right - screenRight);
-            }
-        } else if (rect.left < screenLeft) {
-            scrollXDelta -= (screenLeft - rect.left);
-        }
-
-        if ((scrollYDelta | scrollXDelta) != 0) {
-            return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
-        }
-
-        return false;
-    }
-
-    /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
-            String replace, int newStart, int newEnd) {
-        WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
-        arg.mReplace = replace;
-        arg.mNewStart = newStart;
-        arg.mNewEnd = newEnd;
-        mTextGeneration++;
-        arg.mTextGeneration = mTextGeneration;
-        sendBatchableInputMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
-    }
-
-    /* package */ void passToJavaScript(String currentText, KeyEvent event) {
-        // check if mWebViewCore has been destroyed
-        if (mWebViewCore == null) {
-            return;
-        }
-        WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
-        arg.mEvent = event;
-        arg.mCurrentText = currentText;
-        // Increase our text generation number, and pass it to webcore thread
-        mTextGeneration++;
-        mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
-        // WebKit's document state is not saved until about to leave the page.
-        // To make sure the host application, like Browser, has the up to date
-        // document state when it goes to background, we force to save the
-        // document state.
-        mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
-        mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000);
-    }
-
-    public synchronized WebViewCore getWebViewCore() {
-        return mWebViewCore;
-    }
-
-    private boolean canTextScroll(int directionX, int directionY) {
-        int scrollX = getTextScrollX();
-        int scrollY = getTextScrollY();
-        int maxScrollX = getMaxTextScrollX();
-        int maxScrollY = getMaxTextScrollY();
-        boolean canScrollX = (directionX > 0)
-                ? (scrollX < maxScrollX)
-                : (scrollX > 0);
-        boolean canScrollY = (directionY > 0)
-                ? (scrollY < maxScrollY)
-                : (scrollY > 0);
-        return canScrollX || canScrollY;
-    }
-
-    private int getTextScrollX() {
-        return -mEditTextContent.left;
-    }
-
-    private int getTextScrollY() {
-        return -mEditTextContent.top;
-    }
-
-    private int getMaxTextScrollX() {
-        return Math.max(0, mEditTextContent.width() - mEditTextContentBounds.width());
-    }
-
-    private int getMaxTextScrollY() {
-        return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods can be called from a separate thread, like WebViewCore
-    // If it needs to call the View system, it has to send message.
-    //-------------------------------------------------------------------------
-
-    /**
-     * General handler to receive message coming from webkit thread
-     */
-    class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
-        @Override
-        public void handleMessage(Message msg) {
-            // exclude INVAL_RECT_MSG_ID since it is frequently output
-            if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
-                if (msg.what >= FIRST_PRIVATE_MSG_ID
-                        && msg.what <= LAST_PRIVATE_MSG_ID) {
-                    Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
-                            - FIRST_PRIVATE_MSG_ID]);
-                } else if (msg.what >= FIRST_PACKAGE_MSG_ID
-                        && msg.what <= LAST_PACKAGE_MSG_ID) {
-                    Log.v(LOGTAG, HandlerPackageDebugString[msg.what
-                            - FIRST_PACKAGE_MSG_ID]);
-                } else {
-                    Log.v(LOGTAG, Integer.toString(msg.what));
-                }
-            }
-            if (mWebViewCore == null) {
-                // after WebView's destroy() is called, skip handling messages.
-                return;
-            }
-            if (mBlockWebkitViewMessages
-                    && msg.what != WEBCORE_INITIALIZED_MSG_ID) {
-                // Blocking messages from webkit
-                return;
-            }
-            switch (msg.what) {
-                case REMEMBER_PASSWORD: {
-                    mDatabase.setUsernamePassword(
-                            msg.getData().getString("host"),
-                            msg.getData().getString("username"),
-                            msg.getData().getString("password"));
-                    ((Message) msg.obj).sendToTarget();
-                    break;
-                }
-                case NEVER_REMEMBER_PASSWORD: {
-                    mDatabase.setUsernamePassword(msg.getData().getString("host"), null, null);
-                    ((Message) msg.obj).sendToTarget();
-                    break;
-                }
-                case SCROLL_SELECT_TEXT: {
-                    if (mAutoScrollX == 0 && mAutoScrollY == 0) {
-                        mSentAutoScrollMessage = false;
-                        break;
-                    }
-                    if (mCurrentScrollingLayerId == 0) {
-                        pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
-                    } else {
-                        scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
-                                mScrollingLayerRect.top + mAutoScrollY);
-                    }
-                    sendEmptyMessageDelayed(
-                            SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
-                    break;
-                }
-                case SCROLL_TO_MSG_ID: {
-                    // arg1 = animate, arg2 = onlyIfImeIsShowing
-                    // obj = Point(x, y)
-                    if (msg.arg2 == 1) {
-                        // This scroll is intended to bring the textfield into
-                        // view, but is only necessary if the IME is showing
-                        InputMethodManager imm = InputMethodManager.peekInstance();
-                        if (imm == null || !imm.isAcceptingText()
-                                || !imm.isActive(mWebView)) {
-                            break;
-                        }
-                    }
-                    final Point p = (Point) msg.obj;
-                    contentScrollTo(p.x, p.y, msg.arg1 == 1);
-                    break;
-                }
-                case UPDATE_ZOOM_RANGE: {
-                    WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
-                    // mScrollX contains the new minPrefWidth
-                    mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
-                    break;
-                }
-                case UPDATE_ZOOM_DENSITY: {
-                    final float density = (Float) msg.obj;
-                    mZoomManager.updateDefaultZoomDensity(density);
-                    break;
-                }
-                case NEW_PICTURE_MSG_ID: {
-                    // called for new content
-                    final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
-                    setNewPicture(draw, true);
-                    break;
-                }
-                case WEBCORE_INITIALIZED_MSG_ID:
-                    // nativeCreate sets mNativeClass to a non-zero value
-                    String drawableDir = BrowserFrame.getRawResFilename(
-                            BrowserFrame.DRAWABLEDIR, mContext);
-                    nativeCreate(msg.arg1, drawableDir, ActivityManager.isHighEndGfx());
-                    if (mDelaySetPicture != null) {
-                        setNewPicture(mDelaySetPicture, true);
-                        mDelaySetPicture = null;
-                    }
-                    if (mIsPaused) {
-                        nativeSetPauseDrawing(mNativeClass, true);
-                    }
-                    mInputDispatcher = new WebViewInputDispatcher(this,
-                            mWebViewCore.getInputDispatcherCallbacks());
-                    break;
-                case UPDATE_TEXTFIELD_TEXT_MSG_ID:
-                    // Make sure that the textfield is currently focused
-                    // and representing the same node as the pointer.
-                    if (msg.arg2 == mTextGeneration) {
-                        String text = (String) msg.obj;
-                        if (null == text) {
-                            text = "";
-                        }
-                        if (mInputConnection != null &&
-                                mFieldPointer == msg.arg1) {
-                            mInputConnection.setTextAndKeepSelection(text);
-                        }
-                    }
-                    break;
-                case UPDATE_TEXT_SELECTION_MSG_ID:
-                    updateTextSelectionFromMessage(msg.arg1, msg.arg2,
-                            (WebViewCore.TextSelectionData) msg.obj);
-                    break;
-                case TAKE_FOCUS:
-                    int direction = msg.arg1;
-                    View focusSearch = mWebView.focusSearch(direction);
-                    if (focusSearch != null && focusSearch != mWebView) {
-                        focusSearch.requestFocus();
-                    }
-                    break;
-                case CLEAR_TEXT_ENTRY:
-                    hideSoftKeyboard();
-                    break;
-                case INVAL_RECT_MSG_ID: {
-                    Rect r = (Rect)msg.obj;
-                    if (r == null) {
-                        invalidate();
-                    } else {
-                        // we need to scale r from content into view coords,
-                        // which viewInvalidate() does for us
-                        viewInvalidate(r.left, r.top, r.right, r.bottom);
-                    }
-                    break;
-                }
-                case REQUEST_FORM_DATA:
-                    if (mFieldPointer == msg.arg1) {
-                        ArrayAdapter<String> adapter = (ArrayAdapter<String>)msg.obj;
-                        mAutoCompletePopup.setAdapter(adapter);
-                    }
-                    break;
-
-                case LONG_PRESS_CENTER:
-                    // as this is shared by keydown and trackballdown, reset all
-                    // the states
-                    mGotCenterDown = false;
-                    mTrackballDown = false;
-                    mWebView.performLongClick();
-                    break;
-
-                case WEBCORE_NEED_TOUCH_EVENTS:
-                    mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
-                    break;
-
-                case REQUEST_KEYBOARD:
-                    if (msg.arg1 == 0) {
-                        hideSoftKeyboard();
-                    } else {
-                        displaySoftKeyboard(false);
-                    }
-                    break;
-
-                case DRAG_HELD_MOTIONLESS:
-                    mHeldMotionless = MOTIONLESS_TRUE;
-                    invalidate();
-                    break;
-
-                case SCREEN_ON:
-                    mWebView.setKeepScreenOn(msg.arg1 == 1);
-                    break;
-
-                case EXIT_FULLSCREEN_VIDEO:
-                    if (mHTML5VideoViewProxy != null) {
-                        mHTML5VideoViewProxy.exitFullScreenVideo();
-                    }
-                    break;
-
-                case SHOW_FULLSCREEN: {
-                    View view = (View) msg.obj;
-                    int orientation = msg.arg1;
-                    int npp = msg.arg2;
-
-                    if (inFullScreenMode()) {
-                        Log.w(LOGTAG, "Should not have another full screen.");
-                        dismissFullScreenMode();
-                    }
-                    mFullScreenHolder = new PluginFullScreenHolder(WebViewClassic.this, orientation, npp);
-                    mFullScreenHolder.setContentView(view);
-                    mFullScreenHolder.show();
-                    invalidate();
-
-                    break;
-                }
-                case HIDE_FULLSCREEN:
-                    dismissFullScreenMode();
-                    break;
-
-                case SHOW_RECT_MSG_ID: {
-                    WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
-                    int left = contentToViewX(data.mLeft);
-                    int width = contentToViewDimension(data.mWidth);
-                    int maxWidth = contentToViewDimension(data.mContentWidth);
-                    int viewWidth = getViewWidth();
-                    int x = (int) (left + data.mXPercentInDoc * width -
-                                   data.mXPercentInView * viewWidth);
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
-                              width + ",maxWidth=" + maxWidth +
-                              ",viewWidth=" + viewWidth + ",x="
-                              + x + ",xPercentInDoc=" + data.mXPercentInDoc +
-                              ",xPercentInView=" + data.mXPercentInView+ ")");
-                    }
-                    // use the passing content width to cap x as the current
-                    // mContentWidth may not be updated yet
-                    x = Math.max(0,
-                            (Math.min(maxWidth, x + viewWidth)) - viewWidth);
-                    int top = contentToViewY(data.mTop);
-                    int height = contentToViewDimension(data.mHeight);
-                    int maxHeight = contentToViewDimension(data.mContentHeight);
-                    int viewHeight = getViewHeight();
-                    int y = (int) (top + data.mYPercentInDoc * height -
-                                   data.mYPercentInView * viewHeight);
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
-                              height + ",maxHeight=" + maxHeight +
-                              ",viewHeight=" + viewHeight + ",y="
-                              + y + ",yPercentInDoc=" + data.mYPercentInDoc +
-                              ",yPercentInView=" + data.mYPercentInView+ ")");
-                    }
-                    // use the passing content height to cap y as the current
-                    // mContentHeight may not be updated yet
-                    y = Math.max(0,
-                            (Math.min(maxHeight, y + viewHeight) - viewHeight));
-                    // We need to take into account the visible title height
-                    // when scrolling since y is an absolute view position.
-                    y = Math.max(0, y - getVisibleTitleHeightImpl());
-                    mWebView.scrollTo(x, y);
-                    }
-                    break;
-
-                case CENTER_FIT_RECT:
-                    centerFitRect((Rect)msg.obj);
-                    break;
-
-                case SET_SCROLLBAR_MODES:
-                    mHorizontalScrollBarMode = msg.arg1;
-                    mVerticalScrollBarMode = msg.arg2;
-                    break;
-
-                case FOCUS_NODE_CHANGED:
-                    mIsEditingText = (msg.arg1 == mFieldPointer);
-                    if (mAutoCompletePopup != null && !mIsEditingText) {
-                        mAutoCompletePopup.clearAdapter();
-                    }
-                    // fall through to HIT_TEST_RESULT
-                case HIT_TEST_RESULT:
-                    WebKitHitTest hit = (WebKitHitTest) msg.obj;
-                    mFocusedNode = hit;
-                    setTouchHighlightRects(hit);
-                    setHitTestResult(hit);
-                    break;
-
-                case SAVE_WEBARCHIVE_FINISHED:
-                    SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
-                    if (saveMessage.mCallback != null) {
-                        saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
-                    }
-                    break;
-
-                case SET_AUTOFILLABLE:
-                    mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
-                    if (mInputConnection != null) {
-                        mInputConnection.setAutoFillable(mAutoFillData.getQueryId());
-                        mAutoCompletePopup.setAutoFillQueryId(mAutoFillData.getQueryId());
-                    }
-                    break;
-
-                case AUTOFILL_COMPLETE:
-                    if (mAutoCompletePopup != null) {
-                        ArrayList<String> pastEntries = new ArrayList<String>();
-                        mAutoCompletePopup.setAdapter(new ArrayAdapter<String>(
-                                mContext,
-                                com.android.internal.R.layout.web_text_view_dropdown,
-                                pastEntries));
-                    }
-                    break;
-
-                case COPY_TO_CLIPBOARD:
-                    copyToClipboard((String) msg.obj);
-                    break;
-
-                case INIT_EDIT_FIELD:
-                    if (mInputConnection != null) {
-                        TextFieldInitData initData = (TextFieldInitData) msg.obj;
-                        mTextGeneration = 0;
-                        mFieldPointer = initData.mFieldPointer;
-                        mInputConnection.initEditorInfo(initData);
-                        mInputConnection.setTextAndKeepSelection(initData.mText);
-                        mEditTextContentBounds.set(initData.mContentBounds);
-                        mEditTextLayerId = initData.mNodeLayerId;
-                        nativeMapLayerRect(mNativeClass, mEditTextLayerId,
-                                mEditTextContentBounds);
-                        mEditTextContent.set(initData.mClientRect);
-                        relocateAutoCompletePopup();
-                    }
-                    break;
-
-                case REPLACE_TEXT:{
-                    String text = (String)msg.obj;
-                    int start = msg.arg1;
-                    int end = msg.arg2;
-                    int cursorPosition = start + text.length();
-                    replaceTextfieldText(start, end, text,
-                            cursorPosition, cursorPosition);
-                    selectionDone();
-                    break;
-                }
-
-                case UPDATE_MATCH_COUNT: {
-                    WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj;
-                    if (request == null) {
-                        if (mFindCallback != null) {
-                            mFindCallback.updateMatchCount(0, 0, true);
-                        }
-                    } else if (request == mFindRequest) {
-                        int matchCount, matchIndex;
-                        synchronized (mFindRequest) {
-                            matchCount = request.mMatchCount;
-                            matchIndex = request.mMatchIndex;
-                        }
-                        if (mFindCallback != null) {
-                            mFindCallback.updateMatchCount(matchIndex, matchCount, false);
-                        }
-                        if (mFindListener != null) {
-                            mFindListener.onFindResultReceived(matchIndex, matchCount, true);
-                        }
-                    }
-                    break;
-                }
-
-                case CLEAR_CARET_HANDLE:
-                    if (mIsCaretSelection) {
-                        selectionDone();
-                    }
-                    break;
-
-                case KEY_PRESS:
-                    sendBatchableInputMessage(EventHub.KEY_PRESS, msg.arg1, 0, null);
-                    break;
-
-                case RELOCATE_AUTO_COMPLETE_POPUP:
-                    relocateAutoCompletePopup();
-                    break;
-
-                case AUTOFILL_FORM:
-                    mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM,
-                            msg.arg1, /* unused */0);
-                    break;
-
-                case EDIT_TEXT_SIZE_CHANGED:
-                    if (msg.arg1 == mFieldPointer) {
-                        mEditTextContent.set((Rect)msg.obj);
-                    }
-                    break;
-
-                case SHOW_CARET_HANDLE:
-                    if (!mSelectingText && mIsEditingText && mIsCaretSelection) {
-                        setupWebkitSelect();
-                        resetCaretTimer();
-                        showPasteWindow();
-                    }
-                    break;
-
-                case UPDATE_CONTENT_BOUNDS:
-                    mEditTextContentBounds.set((Rect) msg.obj);
-                    nativeMapLayerRect(mNativeClass, mEditTextLayerId,
-                            mEditTextContentBounds);
-                    break;
-
-                case SCROLL_EDIT_TEXT:
-                    scrollEditWithCursor();
-                    break;
-
-                case SCROLL_HANDLE_INTO_VIEW:
-                    scrollDraggedSelectionHandleIntoView();
-                    break;
-
-                default:
-                    super.handleMessage(msg);
-                    break;
-            }
-        }
-
-        @Override
-        public Looper getUiLooper() {
-            return getLooper();
-        }
-
-        @Override
-        public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
-            onHandleUiEvent(event, eventType, flags);
-        }
-
-        @Override
-        public Context getContext() {
-            return WebViewClassic.this.getContext();
-        }
-
-        @Override
-        public boolean shouldInterceptTouchEvent(MotionEvent event) {
-            if (!mSelectingText) {
-                return false;
-            }
-            ensureSelectionHandles();
-            int y = Math.round(event.getY() - getTitleHeight() + getScrollY());
-            int x = Math.round(event.getX() + getScrollX());
-            boolean isPressingHandle;
-            if (mIsCaretSelection) {
-                isPressingHandle = mSelectHandleCenter.getBounds()
-                        .contains(x, y);
-            } else {
-                isPressingHandle =
-                        mSelectHandleBaseBounds.contains(x, y)
-                        || mSelectHandleExtentBounds.contains(x, y);
-            }
-            return isPressingHandle;
-        }
-
-        @Override
-        public void showTapHighlight(boolean show) {
-            if (mShowTapHighlight != show) {
-                mShowTapHighlight = show;
-                invalidate();
-            }
-        }
-
-        @Override
-        public void clearPreviousHitTest() {
-            setHitTestResult(null);
-        }
-    }
-
-    private void setHitTestTypeFromUrl(String url) {
-        String substr = null;
-        if (url.startsWith(SCHEME_GEO)) {
-            mInitialHitTestResult.setType(HitTestResult.GEO_TYPE);
-            substr = url.substring(SCHEME_GEO.length());
-        } else if (url.startsWith(SCHEME_TEL)) {
-            mInitialHitTestResult.setType(HitTestResult.PHONE_TYPE);
-            substr = url.substring(SCHEME_TEL.length());
-        } else if (url.startsWith(SCHEME_MAILTO)) {
-            mInitialHitTestResult.setType(HitTestResult.EMAIL_TYPE);
-            substr = url.substring(SCHEME_MAILTO.length());
-        } else {
-            mInitialHitTestResult.setType(HitTestResult.SRC_ANCHOR_TYPE);
-            mInitialHitTestResult.setExtra(url);
-            return;
-        }
-        try {
-            mInitialHitTestResult.setExtra(URLDecoder.decode(substr, "UTF-8"));
-        } catch (Throwable e) {
-            Log.w(LOGTAG, "Failed to decode URL! " + substr, e);
-            mInitialHitTestResult.setType(HitTestResult.UNKNOWN_TYPE);
-        }
-    }
-
-    private void setHitTestResult(WebKitHitTest hit) {
-        if (hit == null) {
-            mInitialHitTestResult = null;
-            return;
-        }
-        mInitialHitTestResult = new HitTestResult();
-        if (hit.mLinkUrl != null) {
-            setHitTestTypeFromUrl(hit.mLinkUrl);
-            if (hit.mImageUrl != null
-                    && mInitialHitTestResult.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
-                mInitialHitTestResult.setType(HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
-                mInitialHitTestResult.setExtra(hit.mImageUrl);
-            }
-        } else if (hit.mImageUrl != null) {
-            mInitialHitTestResult.setType(HitTestResult.IMAGE_TYPE);
-            mInitialHitTestResult.setExtra(hit.mImageUrl);
-        } else if (hit.mEditable) {
-            mInitialHitTestResult.setType(HitTestResult.EDIT_TEXT_TYPE);
-        } else if (hit.mIntentUrl != null) {
-            setHitTestTypeFromUrl(hit.mIntentUrl);
-        }
-    }
-
-    private boolean shouldDrawHighlightRect() {
-        if (mFocusedNode == null || mInitialHitTestResult == null) {
-            return false;
-        }
-        if (mTouchHighlightRegion.isEmpty()) {
-            return false;
-        }
-        if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) {
-            return mDrawCursorRing && !mFocusedNode.mEditable;
-        }
-        if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
-            return false;
-        }
-        return mShowTapHighlight;
-    }
-
-
-    private FocusTransitionDrawable mFocusTransition = null;
-    static class FocusTransitionDrawable extends Drawable {
-        Region mPreviousRegion;
-        Region mNewRegion;
-        float mProgress = 0;
-        WebViewClassic mWebView;
-        Paint mPaint;
-        int mMaxAlpha;
-        Point mTranslate;
-
-        public FocusTransitionDrawable(WebViewClassic view) {
-            mWebView = view;
-            mPaint = new Paint(mWebView.mTouchHightlightPaint);
-            mMaxAlpha = mPaint.getAlpha();
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter cf) {
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-        }
-
-        @Override
-        public int getOpacity() {
-            return 0;
-        }
-
-        public void setProgress(float p) {
-            mProgress = p;
-            if (mWebView.mFocusTransition == this) {
-                if (mProgress == 1f)
-                    mWebView.mFocusTransition = null;
-                mWebView.invalidate();
-            }
-        }
-
-        public float getProgress() {
-            return mProgress;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            if (mTranslate == null) {
-                Rect bounds = mPreviousRegion.getBounds();
-                Point from = new Point(bounds.centerX(), bounds.centerY());
-                mNewRegion.getBounds(bounds);
-                Point to = new Point(bounds.centerX(), bounds.centerY());
-                mTranslate = new Point(from.x - to.x, from.y - to.y);
-            }
-            int alpha = (int) (mProgress * mMaxAlpha);
-            RegionIterator iter = new RegionIterator(mPreviousRegion);
-            Rect r = new Rect();
-            mPaint.setAlpha(mMaxAlpha - alpha);
-            float tx = mTranslate.x * mProgress;
-            float ty = mTranslate.y * mProgress;
-            int save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            canvas.translate(-tx, -ty);
-            while (iter.next(r)) {
-                canvas.drawRect(r, mPaint);
-            }
-            canvas.restoreToCount(save);
-            iter = new RegionIterator(mNewRegion);
-            r = new Rect();
-            mPaint.setAlpha(alpha);
-            save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            tx = mTranslate.x - tx;
-            ty = mTranslate.y - ty;
-            canvas.translate(tx, ty);
-            while (iter.next(r)) {
-                canvas.drawRect(r, mPaint);
-            }
-            canvas.restoreToCount(save);
-        }
-    };
-
-    private boolean shouldAnimateTo(WebKitHitTest hit) {
-        // TODO: Don't be annoying or throw out the animation entirely
-        return false;
-    }
-
-    private void setTouchHighlightRects(WebKitHitTest hit) {
-        FocusTransitionDrawable transition = null;
-        if (shouldAnimateTo(hit)) {
-            transition = new FocusTransitionDrawable(this);
-        }
-        Rect[] rects = hit != null ? hit.mTouchRects : null;
-        if (!mTouchHighlightRegion.isEmpty()) {
-            mWebView.invalidate(mTouchHighlightRegion.getBounds());
-            if (transition != null) {
-                transition.mPreviousRegion = new Region(mTouchHighlightRegion);
-            }
-            mTouchHighlightRegion.setEmpty();
-        }
-        if (rects != null) {
-            mTouchHightlightPaint.setColor(hit.mTapHighlightColor);
-            for (Rect rect : rects) {
-                Rect viewRect = contentToViewRect(rect);
-                // some sites, like stories in nytimes.com, set
-                // mouse event handler in the top div. It is not
-                // user friendly to highlight the div if it covers
-                // more than half of the screen.
-                if (viewRect.width() < getWidth() >> 1
-                        || viewRect.height() < getHeight() >> 1) {
-                    mTouchHighlightRegion.union(viewRect);
-                } else if (DebugFlags.WEB_VIEW) {
-                    Log.d(LOGTAG, "Skip the huge selection rect:"
-                            + viewRect);
-                }
-            }
-            mWebView.invalidate(mTouchHighlightRegion.getBounds());
-            if (transition != null && transition.mPreviousRegion != null) {
-                transition.mNewRegion = new Region(mTouchHighlightRegion);
-                mFocusTransition = transition;
-                ObjectAnimator animator = ObjectAnimator.ofFloat(
-                        mFocusTransition, "progress", 1f);
-                animator.start();
-            }
-        }
-    }
-
-    // Interface to allow the profiled WebView to hook the page swap notifications.
-    public interface PageSwapDelegate {
-        void onPageSwapOccurred(boolean notifyAnimationStarted);
-    }
-
-    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);
-        }
-        if (mWebView instanceof PageSwapDelegate) {
-            // This provides a hook for ProfiledWebView to observe the tile page swaps.
-            ((PageSwapDelegate) mWebView).onPageSwapOccurred(notifyAnimationStarted);
-        }
-
-        if (mPictureListener != null) {
-            // trigger picture listener for hardware layers. Software layers are
-            // triggered in setNewPicture
-            Picture picture = mContext.getApplicationInfo().targetSdkVersion <
-                    Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
-            if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
-            mPictureListener.onNewPicture(getWebView(), picture);
-        }
-    }
-
-    void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
-        if (mNativeClass == 0) {
-            if (mDelaySetPicture != null) {
-                throw new IllegalStateException("Tried to setNewPicture with"
-                        + " a delay picture already set! (memory leak)");
-            }
-            // Not initialized yet, delay set
-            mDelaySetPicture = draw;
-            return;
-        }
-        WebViewCore.ViewState viewState = draw.mViewState;
-        boolean isPictureAfterFirstLayout = viewState != null;
-
-        if (updateBaseLayer) {
-            setBaseLayer(draw.mBaseLayer,
-                    getSettings().getShowVisualIndicator(),
-                    isPictureAfterFirstLayout);
-        }
-        final Point viewSize = draw.mViewSize;
-        // We update the layout (i.e. request a layout from the
-        // view system) if the last view size that we sent to
-        // WebCore matches the view size of the picture we just
-        // received in the fixed dimension.
-        final boolean updateLayout = viewSize.x == mLastWidthSent
-                && viewSize.y == mLastHeightSent;
-        // Don't send scroll event for picture coming from webkit,
-        // since the new picture may cause a scroll event to override
-        // the saved history scroll position.
-        mSendScrollEvent = false;
-        recordNewContentSize(draw.mContentSize.x,
-                draw.mContentSize.y, updateLayout);
-        if (isPictureAfterFirstLayout) {
-            // Reset the last sent data here since dealing with new page.
-            mLastWidthSent = 0;
-            mZoomManager.onFirstLayout(draw);
-            int scrollX = viewState.mShouldStartScrolledRight
-                    ? getContentWidth() : viewState.mScrollX;
-            int scrollY = viewState.mScrollY;
-            contentScrollTo(scrollX, scrollY, false);
-            if (!mDrawHistory) {
-                // As we are on a new page, hide the keyboard
-                hideSoftKeyboard();
-            }
-        }
-        mSendScrollEvent = true;
-
-        int functor = 0;
-        boolean forceInval = isPictureAfterFirstLayout;
-        ViewRootImpl viewRoot = mWebView.getViewRootImpl();
-        if (mWebView.isHardwareAccelerated()
-                && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE
-                && viewRoot != null) {
-            functor = nativeGetDrawGLFunction(mNativeClass);
-            if (functor != 0) {
-                // force an invalidate if functor attach not successful
-                forceInval |= !viewRoot.attachFunctor(functor);
-            }
-        }
-
-        if (functor == 0
-                || forceInval
-                || mWebView.getLayerType() != View.LAYER_TYPE_NONE) {
-            // invalidate the screen so that the next repaint will show new content
-            // TODO: partial invalidate
-            mWebView.invalidate();
-        }
-
-        // update the zoom information based on the new picture
-        if (mZoomManager.onNewPicture(draw))
-            invalidate();
-
-        if (isPictureAfterFirstLayout) {
-            mViewManager.postReadyToDrawAll();
-        }
-        scrollEditWithCursor();
-
-        if (mPictureListener != null) {
-            if (!mWebView.isHardwareAccelerated()
-                    || mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
-                // trigger picture listener for software layers. Hardware layers are
-                // triggered in pageSwapCallback
-                Picture picture = mContext.getApplicationInfo().targetSdkVersion <
-                        Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
-                if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
-                mPictureListener.onNewPicture(getWebView(), picture);
-            }
-        }
-    }
-
-    /**
-     * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
-     * and UPDATE_TEXT_SELECTION_MSG_ID.
-     */
-    private void updateTextSelectionFromMessage(int nodePointer,
-            int textGeneration, WebViewCore.TextSelectionData data) {
-        if (textGeneration == mTextGeneration) {
-            if (mInputConnection != null && mFieldPointer == nodePointer) {
-                mInputConnection.setSelection(data.mStart, data.mEnd);
-            }
-        }
-        nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
-
-        if ((data.mSelectionReason == TextSelectionData.REASON_ACCESSIBILITY_INJECTOR)
-                || (!mSelectingText && data.mStart != data.mEnd
-                        && data.mSelectionReason != TextSelectionData.REASON_SELECT_WORD)) {
-            selectionDone();
-            mShowTextSelectionExtra = true;
-            invalidate();
-            return;
-        }
-
-        if (data.mSelectTextPtr != 0 &&
-                (data.mStart != data.mEnd ||
-                (mFieldPointer == nodePointer && mFieldPointer != 0) ||
-                (nodePointer == 0 && data.mStart == 0 && data.mEnd == 0))) {
-            mIsEditingText = (mFieldPointer == nodePointer) && nodePointer != 0;
-            mIsCaretSelection = (data.mStart == data.mEnd && nodePointer != 0);
-            if (mIsCaretSelection &&
-                    (mInputConnection == null ||
-                    mInputConnection.getEditable().length() == 0)) {
-                // There's no text, don't show caret handle.
-                selectionDone();
-            } else {
-                if (!mSelectingText) {
-                    setupWebkitSelect();
-                } else {
-                    syncSelectionCursors();
-                }
-                animateHandles();
-                if (mIsCaretSelection) {
-                    resetCaretTimer();
-                }
-            }
-        } else {
-            selectionDone();
-        }
-        invalidate();
-    }
-
-    private void scrollEditText(int scrollX, int scrollY) {
-        // Scrollable edit text. Scroll it.
-        float maxScrollX = getMaxTextScrollX();
-        float scrollPercentX = ((float)scrollX)/maxScrollX;
-        mEditTextContent.offsetTo(-scrollX, -scrollY);
-        mWebViewCore.removeMessages(EventHub.SCROLL_TEXT_INPUT);
-        mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0,
-                scrollY, (Float)scrollPercentX);
-        animateHandles();
-    }
-
-    private void beginTextBatch() {
-        mIsBatchingTextChanges = true;
-    }
-
-    private void commitTextBatch() {
-        if (mWebViewCore != null) {
-            mWebViewCore.sendMessages(mBatchedTextChanges);
-        }
-        mBatchedTextChanges.clear();
-        mIsBatchingTextChanges = false;
-    }
-
-    void sendBatchableInputMessage(int what, int arg1, int arg2,
-            Object obj) {
-        if (mWebViewCore == null) {
-            return;
-        }
-        Message message = Message.obtain(null, what, arg1, arg2, obj);
-        if (mIsBatchingTextChanges) {
-            mBatchedTextChanges.add(message);
-        } else {
-            mWebViewCore.sendMessage(message);
-        }
-    }
-
-    // Class used to use a dropdown for a <select> element
-    private class InvokeListBox implements Runnable {
-        // Whether the listbox allows multiple selection.
-        private boolean     mMultiple;
-        // Passed in to a list with multiple selection to tell
-        // which items are selected.
-        private int[]       mSelectedArray;
-        // Passed in to a list with single selection to tell
-        // where the initial selection is.
-        private int         mSelection;
-
-        private Container[] mContainers;
-
-        // Need these to provide stable ids to my ArrayAdapter,
-        // which normally does not have stable ids. (Bug 1250098)
-        private class Container extends Object {
-            /**
-             * Possible values for mEnabled.  Keep in sync with OptionStatus in
-             * WebViewCore.cpp
-             */
-            final static int OPTGROUP = -1;
-            final static int OPTION_DISABLED = 0;
-            final static int OPTION_ENABLED = 1;
-
-            String  mString;
-            int     mEnabled;
-            int     mId;
-
-            @Override
-            public String toString() {
-                return mString;
-            }
-        }
-
-        /**
-         *  Subclass ArrayAdapter so we can disable OptionGroupLabels,
-         *  and allow filtering.
-         */
-        private class MyArrayListAdapter extends ArrayAdapter<Container> {
-            public MyArrayListAdapter() {
-                super(WebViewClassic.this.mContext,
-                        mMultiple ? com.android.internal.R.layout.select_dialog_multichoice :
-                        com.android.internal.R.layout.webview_select_singlechoice,
-                        mContainers);
-            }
-
-            @Override
-            public View getView(int position, View convertView,
-                    ViewGroup parent) {
-                // Always pass in null so that we will get a new CheckedTextView
-                // Otherwise, an item which was previously used as an <optgroup>
-                // element (i.e. has no check), could get used as an <option>
-                // element, which needs a checkbox/radio, but it would not have
-                // one.
-                convertView = super.getView(position, null, parent);
-                Container c = item(position);
-                if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
-                    // ListView does not draw dividers between disabled and
-                    // enabled elements.  Use a LinearLayout to provide dividers
-                    LinearLayout layout = new LinearLayout(mContext);
-                    layout.setOrientation(LinearLayout.VERTICAL);
-                    if (position > 0) {
-                        View dividerTop = new View(mContext);
-                        dividerTop.setBackgroundResource(
-                                android.R.drawable.divider_horizontal_bright);
-                        layout.addView(dividerTop);
-                    }
-
-                    if (Container.OPTGROUP == c.mEnabled) {
-                        // Currently select_dialog_multichoice uses CheckedTextViews.
-                        // If that changes, the class cast will no longer be valid.
-                        if (mMultiple) {
-                            Assert.assertTrue(convertView instanceof CheckedTextView);
-                            ((CheckedTextView) convertView).setCheckMarkDrawable(null);
-                        }
-                    } else {
-                        // c.mEnabled == Container.OPTION_DISABLED
-                        // Draw the disabled element in a disabled state.
-                        convertView.setEnabled(false);
-                    }
-
-                    layout.addView(convertView);
-                    if (position < getCount() - 1) {
-                        View dividerBottom = new View(mContext);
-                        dividerBottom.setBackgroundResource(
-                                android.R.drawable.divider_horizontal_bright);
-                        layout.addView(dividerBottom);
-                    }
-                    return layout;
-                }
-                return convertView;
-            }
-
-            @Override
-            public boolean hasStableIds() {
-                // AdapterView's onChanged method uses this to determine whether
-                // to restore the old state.  Return false so that the old (out
-                // of date) state does not replace the new, valid state.
-                return false;
-            }
-
-            private Container item(int position) {
-                if (position < 0 || position >= getCount()) {
-                    return null;
-                }
-                return getItem(position);
-            }
-
-            @Override
-            public long getItemId(int position) {
-                Container item = item(position);
-                if (item == null) {
-                    return -1;
-                }
-                return item.mId;
-            }
-
-            @Override
-            public boolean areAllItemsEnabled() {
-                return false;
-            }
-
-            @Override
-            public boolean isEnabled(int position) {
-                Container item = item(position);
-                if (item == null) {
-                    return false;
-                }
-                return Container.OPTION_ENABLED == item.mEnabled;
-            }
-        }
-
-        private InvokeListBox(String[] array, int[] enabled, int[] selected) {
-            mMultiple = true;
-            mSelectedArray = selected;
-
-            int length = array.length;
-            mContainers = new Container[length];
-            for (int i = 0; i < length; i++) {
-                mContainers[i] = new Container();
-                mContainers[i].mString = array[i];
-                mContainers[i].mEnabled = enabled[i];
-                mContainers[i].mId = i;
-            }
-        }
-
-        private InvokeListBox(String[] array, int[] enabled, int selection) {
-            mSelection = selection;
-            mMultiple = false;
-
-            int length = array.length;
-            mContainers = new Container[length];
-            for (int i = 0; i < length; i++) {
-                mContainers[i] = new Container();
-                mContainers[i].mString = array[i];
-                mContainers[i].mEnabled = enabled[i];
-                mContainers[i].mId = i;
-            }
-        }
-
-        /*
-         * Whenever the data set changes due to filtering, this class ensures
-         * that the checked item remains checked.
-         */
-        private class SingleDataSetObserver extends DataSetObserver {
-            private long        mCheckedId;
-            private ListView    mListView;
-            private Adapter     mAdapter;
-
-            /*
-             * Create a new observer.
-             * @param id The ID of the item to keep checked.
-             * @param l ListView for getting and clearing the checked states
-             * @param a Adapter for getting the IDs
-             */
-            public SingleDataSetObserver(long id, ListView l, Adapter a) {
-                mCheckedId = id;
-                mListView = l;
-                mAdapter = a;
-            }
-
-            @Override
-            public void onChanged() {
-                // The filter may have changed which item is checked.  Find the
-                // item that the ListView thinks is checked.
-                int position = mListView.getCheckedItemPosition();
-                long id = mAdapter.getItemId(position);
-                if (mCheckedId != id) {
-                    // Clear the ListView's idea of the checked item, since
-                    // it is incorrect
-                    mListView.clearChoices();
-                    // Search for mCheckedId.  If it is in the filtered list,
-                    // mark it as checked
-                    int count = mAdapter.getCount();
-                    for (int i = 0; i < count; i++) {
-                        if (mAdapter.getItemId(i) == mCheckedId) {
-                            mListView.setItemChecked(i, true);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void run() {
-            if (mWebViewCore == null
-                    || getWebView().getWindowToken() == null
-                    || getWebView().getViewRootImpl() == null) {
-                // We've been detached and/or destroyed since this was posted
-                return;
-            }
-            final ListView listView = (ListView) LayoutInflater.from(mContext)
-                    .inflate(com.android.internal.R.layout.select_dialog, null);
-            final MyArrayListAdapter adapter = new MyArrayListAdapter();
-            AlertDialog.Builder b = new AlertDialog.Builder(mContext)
-                    .setView(listView).setCancelable(true)
-                    .setInverseBackgroundForced(true);
-
-            if (mMultiple) {
-                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        mWebViewCore.sendMessage(
-                                EventHub.LISTBOX_CHOICES,
-                                adapter.getCount(), 0,
-                                listView.getCheckedItemPositions());
-                    }});
-                b.setNegativeButton(android.R.string.cancel,
-                        new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        mWebViewCore.sendMessage(
-                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
-                }});
-            }
-            mListBoxDialog = b.create();
-            listView.setAdapter(adapter);
-            listView.setFocusableInTouchMode(true);
-            // There is a bug (1250103) where the checks in a ListView with
-            // multiple items selected are associated with the positions, not
-            // the ids, so the items do not properly retain their checks when
-            // filtered.  Do not allow filtering on multiple lists until
-            // that bug is fixed.
-
-            listView.setTextFilterEnabled(!mMultiple);
-            if (mMultiple) {
-                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
-                int length = mSelectedArray.length;
-                for (int i = 0; i < length; i++) {
-                    listView.setItemChecked(mSelectedArray[i], true);
-                }
-            } else {
-                listView.setOnItemClickListener(new OnItemClickListener() {
-                    @Override
-                    public void onItemClick(AdapterView<?> parent, View v,
-                            int position, long id) {
-                        // Rather than sending the message right away, send it
-                        // after the page regains focus.
-                        mListBoxMessage = Message.obtain(null,
-                                EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
-                        if (mListBoxDialog != null) {
-                            mListBoxDialog.dismiss();
-                            mListBoxDialog = null;
-                        }
-                    }
-                });
-                if (mSelection != -1) {
-                    listView.setSelection(mSelection);
-                    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
-                    listView.setItemChecked(mSelection, true);
-                    DataSetObserver observer = new SingleDataSetObserver(
-                            adapter.getItemId(mSelection), listView, adapter);
-                    adapter.registerDataSetObserver(observer);
-                }
-            }
-            mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
-                @Override
-                public void onCancel(DialogInterface dialog) {
-                 if (mWebViewCore != null) {
-                    mWebViewCore.sendMessage(
-                                EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
-                    }
-                    mListBoxDialog = null;
-                }
-            });
-            mListBoxDialog.show();
-        }
-    }
-
-    private Message mListBoxMessage;
-
-    /*
-     * Request a dropdown menu for a listbox with multiple selection.
-     *
-     * @param array Labels for the listbox.
-     * @param enabledArray  State for each element in the list.  See static
-     *      integers in Container class.
-     * @param selectedArray Which positions are initally selected.
-     */
-    void requestListBox(String[] array, int[] enabledArray, int[]
-            selectedArray) {
-        mPrivateHandler.post(
-                new InvokeListBox(array, enabledArray, selectedArray));
-    }
-
-    /*
-     * Request a dropdown menu for a listbox with single selection or a single
-     * <select> element.
-     *
-     * @param array Labels for the listbox.
-     * @param enabledArray  State for each element in the list.  See static
-     *      integers in Container class.
-     * @param selection Which position is initally selected.
-     */
-    void requestListBox(String[] array, int[] enabledArray, int selection) {
-        mPrivateHandler.post(
-                new InvokeListBox(array, enabledArray, selection));
-    }
-
-    private int getScaledMaxXScroll() {
-        int width;
-        if (mHeightCanMeasure == false) {
-            width = getViewWidth() / 4;
-        } else {
-            Rect visRect = new Rect();
-            calcOurVisibleRect(visRect);
-            width = visRect.width() / 2;
-        }
-        // FIXME the divisor should be retrieved from somewhere
-        return viewToContentX(width);
-    }
-
-    private int getScaledMaxYScroll() {
-        int height;
-        if (mHeightCanMeasure == false) {
-            height = getViewHeight() / 4;
-        } else {
-            Rect visRect = new Rect();
-            calcOurVisibleRect(visRect);
-            height = visRect.height() / 2;
-        }
-        // FIXME the divisor should be retrieved from somewhere
-        // the closest thing today is hard-coded into ScrollView.java
-        // (from ScrollView.java, line 363)   int maxJump = height/2;
-        return Math.round(height * mZoomManager.getInvScale());
-    }
-
-    /**
-     * Called by JNI to invalidate view
-     */
-    private void viewInvalidate() {
-        invalidate();
-    }
-
-    /**
-     * Pass the key directly to the page.  This assumes that
-     * nativePageShouldHandleShiftAndArrows() returned true.
-     */
-    private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
-        int keyEventAction;
-        if (down) {
-            keyEventAction = KeyEvent.ACTION_DOWN;
-        } else {
-            keyEventAction = KeyEvent.ACTION_UP;
-        }
-
-        KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
-                1, (metaState & KeyEvent.META_SHIFT_ON)
-                | (metaState & KeyEvent.META_ALT_ON)
-                | (metaState & KeyEvent.META_SYM_ON)
-                , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
-        sendKeyEvent(event);
-    }
-
-    private void sendKeyEvent(KeyEvent event) {
-        int direction = 0;
-        switch (event.getKeyCode()) {
-        case KeyEvent.KEYCODE_DPAD_DOWN:
-            direction = View.FOCUS_DOWN;
-            break;
-        case KeyEvent.KEYCODE_DPAD_UP:
-            direction = View.FOCUS_UP;
-            break;
-        case KeyEvent.KEYCODE_DPAD_LEFT:
-            direction = View.FOCUS_LEFT;
-            break;
-        case KeyEvent.KEYCODE_DPAD_RIGHT:
-            direction = View.FOCUS_RIGHT;
-            break;
-        case KeyEvent.KEYCODE_TAB:
-            direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
-            break;
-        }
-        if (direction != 0 && mWebView.focusSearch(direction) == null) {
-            // Can't take focus in that direction
-            direction = 0;
-        }
-        int eventHubAction = EventHub.KEY_UP;
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            eventHubAction = EventHub.KEY_DOWN;
-            int sound = keyCodeToSoundsEffect(event.getKeyCode());
-            if (sound != 0) {
-                mWebView.playSoundEffect(sound);
-            }
-        }
-        sendBatchableInputMessage(eventHubAction, direction, 0, event);
-    }
-
-    /**
-     * See {@link WebView#setBackgroundColor(int)}
-     */
-    @Override
-    public void setBackgroundColor(int color) {
-        mBackgroundColor = color;
-        mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
-    }
-
-    /**
-     * Enable the communication b/t the webView and VideoViewProxy
-     *
-     * only used by the Browser
-     */
-    public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
-        mHTML5VideoViewProxy = proxy;
-    }
-
-    /**
-     * Set the time to wait between passing touches to WebCore. See also the
-     * TOUCH_SENT_INTERVAL member for further discussion.
-     *
-     * This is only used by the DRT test application.
-     */
-    public void setTouchInterval(int interval) {
-        mCurrentTouchInterval = interval;
-    }
-
-    /**
-     * Copy text into the clipboard. This is called indirectly from
-     * WebViewCore.
-     * @param text The text to put into the clipboard.
-     */
-    private void copyToClipboard(String text) {
-        ClipboardManager cm = (ClipboardManager)mContext
-                .getSystemService(Context.CLIPBOARD_SERVICE);
-        ClipData clip = ClipData.newPlainText(getTitle(), text);
-        cm.setPrimaryClip(clip);
-    }
-
-    /*package*/ void autoFillForm(int autoFillQueryId) {
-        mPrivateHandler.obtainMessage(AUTOFILL_FORM, autoFillQueryId, 0)
-            .sendToTarget();
-    }
-
-    /* package */ ViewManager getViewManager() {
-        return mViewManager;
-    }
-
-    /** send content invalidate */
-    protected void contentInvalidateAll() {
-        if (mWebViewCore != null && !mBlockWebkitViewMessages) {
-            mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL);
-        }
-    }
-
-    /** discard all textures from tiles. Used in Profiled WebView */
-    public void discardAllTextures() {
-        nativeDiscardAllTextures();
-    }
-
-    @Override
-    public void setLayerType(int layerType, Paint paint) {
-        updateHwAccelerated();
-    }
-
-    @Override
-    public void preDispatchDraw(Canvas canvas) {
-        // no-op for WebViewClassic.
-    }
-
-    private void updateHwAccelerated() {
-        if (mNativeClass == 0) {
-            return;
-        }
-        boolean hwAccelerated = false;
-        if (mWebView.isHardwareAccelerated()
-                && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
-            hwAccelerated = true;
-        }
-
-        // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw
-        int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
-        if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) {
-            mWebViewCore.contentDraw();
-        }
-    }
-
-    /**
-     * Begin collecting per-tile profiling data
-     *
-     * only used by profiling tests
-     */
-    public void tileProfilingStart() {
-        nativeTileProfilingStart();
-    }
-    /**
-     * Return per-tile profiling data
-     *
-     * only used by profiling tests
-     */
-    public float tileProfilingStop() {
-        return nativeTileProfilingStop();
-    }
-
-    /** only used by profiling tests */
-    public void tileProfilingClear() {
-        nativeTileProfilingClear();
-    }
-    /** only used by profiling tests */
-    public int tileProfilingNumFrames() {
-        return nativeTileProfilingNumFrames();
-    }
-    /** only used by profiling tests */
-    public int tileProfilingNumTilesInFrame(int frame) {
-        return nativeTileProfilingNumTilesInFrame(frame);
-    }
-    /** only used by profiling tests */
-    public int tileProfilingGetInt(int frame, int tile, String key) {
-        return nativeTileProfilingGetInt(frame, tile, key);
-    }
-    /** only used by profiling tests */
-    public float tileProfilingGetFloat(int frame, int tile, String key) {
-        return nativeTileProfilingGetFloat(frame, tile, key);
-    }
-
-    /**
-     * Checks the focused content for an editable text field. This can be
-     * text input or ContentEditable.
-     * @return true if the focused item is an editable text field.
-     */
-    boolean focusCandidateIsEditableText() {
-        if (mFocusedNode != null) {
-            return mFocusedNode.mEditable;
-        }
-        return false;
-    }
-
-    // Called via JNI
-    private void postInvalidate() {
-        mWebView.postInvalidate();
-    }
-
-    // Note: must be called before first WebViewClassic is created.
-    public static void setShouldMonitorWebCoreThread() {
-        WebViewCore.setShouldMonitorWebCoreThread();
-    }
-
-    @Override
-    public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
-        int layer = getBaseLayer();
-        if (layer != 0) {
-            try {
-                ByteArrayOutputStream stream = new ByteArrayOutputStream();
-                ViewStateSerializer.dumpLayerHierarchy(layer, stream, level);
-                stream.close();
-                byte[] buf = stream.toByteArray();
-                out.write(new String(buf, "ascii"));
-            } catch (IOException e) {}
-        }
-    }
-
-    @Override
-    public View findHierarchyView(String className, int hashCode) {
-        if (mNativeClass == 0) return null;
-        Picture pic = new Picture();
-        if (!nativeDumpLayerContentToPicture(mNativeClass, className, hashCode, pic)) {
-            return null;
-        }
-        return new PictureWrapperView(getContext(), pic, mWebView);
-    }
-
-    private static class PictureWrapperView extends View {
-        Picture mPicture;
-        WebView mWebView;
-
-        public PictureWrapperView(Context context, Picture picture, WebView parent) {
-            super(context);
-            mPicture = picture;
-            mWebView = parent;
-            setWillNotDraw(false);
-            setRight(mPicture.getWidth());
-            setBottom(mPicture.getHeight());
-        }
-
-        @Override
-        protected void onDraw(Canvas canvas) {
-            canvas.drawPicture(mPicture);
-        }
-
-        @Override
-        public boolean post(Runnable action) {
-            return mWebView.post(action);
-        }
-    }
-
-    private native void     nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx);
-    private native void     nativeDebugDump();
-    private static native void nativeDestroy(int ptr);
-
-    private native void nativeDraw(Canvas canvas, RectF visibleRect,
-            int color, int extra);
-    private native void     nativeDumpDisplayTree(String urlOrNull);
-    private native boolean  nativeEvaluateLayersAnimations(int nativeInstance);
-    private native int      nativeCreateDrawGLFunction(int nativeInstance, Rect invScreenRect,
-            Rect screenRect, RectF visibleContentRect, float scale, int extras);
-    private native int      nativeGetDrawGLFunction(int nativeInstance);
-    private native void     nativeUpdateDrawGLFunction(int nativeInstance, Rect invScreenRect,
-            Rect screenRect, RectF visibleContentRect, float scale);
-    private native String   nativeGetSelection();
-    private native void     nativeSetHeightCanMeasure(boolean measure);
-    private native boolean  nativeSetBaseLayer(int nativeInstance,
-            int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout,
-            int scrollingLayer);
-    private native int      nativeGetBaseLayer(int nativeInstance);
-    private native void     nativeCopyBaseContentToPicture(Picture pict);
-    private native boolean     nativeDumpLayerContentToPicture(int nativeInstance,
-            String className, int layerId, Picture pict);
-    private native boolean  nativeHasContent();
-    private native void     nativeStopGL(int ptr);
-    private native void     nativeDiscardAllTextures();
-    private native void     nativeTileProfilingStart();
-    private native float    nativeTileProfilingStop();
-    private native void     nativeTileProfilingClear();
-    private native int      nativeTileProfilingNumFrames();
-    private native int      nativeTileProfilingNumTilesInFrame(int frame);
-    private native int      nativeTileProfilingGetInt(int frame, int tile, String key);
-    private native float    nativeTileProfilingGetFloat(int frame, int tile, String key);
-
-    private native void     nativeUseHardwareAccelSkia(boolean enabled);
-
-    // Returns a pointer to the scrollable LayerAndroid at the given point.
-    private native int      nativeScrollableLayer(int nativeInstance, int x, int y, Rect scrollRect,
-            Rect scrollBounds);
-    /**
-     * Scroll the specified layer.
-     * @param nativeInstance Native WebView instance
-     * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer.
-     * @param newX Destination x position to which to scroll.
-     * @param newY Destination y position to which to scroll.
-     * @return True if the layer is successfully scrolled.
-     */
-    private native boolean  nativeScrollLayer(int nativeInstance, int layer, int newX, int newY);
-    private native void     nativeSetIsScrolling(boolean isScrolling);
-    private native int      nativeGetBackgroundColor(int nativeInstance);
-    native boolean  nativeSetProperty(String key, String value);
-    native String   nativeGetProperty(String key);
-    /**
-     * See {@link ComponentCallbacks2} for the trim levels and descriptions
-     */
-    private static native void     nativeOnTrimMemory(int level);
-    private static native void nativeSetPauseDrawing(int instance, boolean pause);
-    private static native void nativeSetTextSelection(int instance, int selection);
-    private static native int nativeGetHandleLayerId(int instance, int handle,
-            Point cursorLocation, QuadF textQuad);
-    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);
-    private static native void nativeFindMaxVisibleRect(int instance, int layerId,
-            Rect visibleContentRect);
-    private static native boolean nativeIsHandleLeft(int instance, int handleId);
-    private static native boolean nativeIsPointVisible(int instance,
-            int layerId, int contentX, int contentY);
-}
diff --git a/core/java/android/webkit/WebViewClientClassicExt.java b/core/java/android/webkit/WebViewClientClassicExt.java
deleted file mode 100644
index a873585..0000000
--- a/core/java/android/webkit/WebViewClientClassicExt.java
+++ /dev/null
@@ -1,53 +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;
-
-import android.net.http.SslError;
-
-/**
- * Adds WebViewClassic specific extension methods to the WebViewClient callback class.
- * These are not part of the public WebView API, so the class is hidden.
- * @hide
- */
-public class WebViewClientClassicExt extends WebViewClient {
-
-    /**
-     * Notify the host application that an SSL error occurred while loading a
-     * resource, but the WebView chose to proceed anyway based on a
-     * decision retained from a previous response to onReceivedSslError().
-     */
-    public void onProceededAfterSslError(WebView view, SslError error) {
-    }
-
-    /**
-     * Notify the host application to handle a SSL client certificate
-     * request (display the request to the user and ask whether to
-     * proceed with a client certificate or not). The host application
-     * has to call either handler.cancel() or handler.proceed() as the
-     * connection is suspended and waiting for the response. The
-     * default behavior is to cancel, returning no client certificate.
-     *
-     * @param view The WebView that is initiating the callback.
-     * @param handler A ClientCertRequestHandler object that will
-     *            handle the user's response.
-     * @param host_and_port The host and port of the requesting server.
-     */
-    public void onReceivedClientCertRequest(WebView view,
-            ClientCertRequestHandler handler, String host_and_port) {
-        handler.cancel();
-    }
-}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
deleted file mode 100644
index 4a09636..0000000
--- a/core/java/android/webkit/WebViewCore.java
+++ /dev/null
@@ -1,3145 +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.
- */
-
-package android.webkit;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.Cursor;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.media.MediaFile;
-import android.net.ProxyProperties;
-import android.net.Uri;
-import android.net.http.CertificateChainValidator;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.View;
-import android.webkit.WebViewClassic.FocusNodeHref;
-import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
-
-import com.android.internal.os.SomeArgs;
-
-import junit.framework.Assert;
-
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @hide
- */
-public final class WebViewCore {
-
-    private static final String LOGTAG = "webcore";
-
-    static {
-        // Load libwebcore and libchromium_net during static initialization.
-        // This happens in the zygote process so they will be shared read-only
-        // across all app processes.
-        try {
-            System.loadLibrary("webcore");
-            System.loadLibrary("chromium_net");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(LOGTAG, "Unable to load native support libraries.");
-        }
-    }
-
-    /*
-     * WebViewCore always executes in the same thread as the native webkit.
-     */
-
-    // 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
-    private final WebSettingsClassic mSettings;
-    // Context for initializing the BrowserFrame with the proper assets.
-    private final Context mContext;
-    // The pointer to a native view object.
-    private int mNativeClass;
-    // The BrowserFrame is an interface to the native Frame component.
-    private BrowserFrame mBrowserFrame;
-    // Custom JS interfaces to add during the initialization.
-    private Map<String, Object> mJavascriptInterfaces;
-    /*
-     * range is from 200 to 10,000. 0 is a special value means device-width. -1
-     * means undefined.
-     */
-    private int mViewportWidth = -1;
-
-    /*
-     * range is from 200 to 10,000. 0 is a special value means device-height. -1
-     * means undefined.
-     */
-    private int mViewportHeight = -1;
-
-    /*
-     * scale in percent, range is from 1 to 1000. 0 means undefined.
-     */
-    private int mViewportInitialScale = 0;
-
-    /*
-     * scale in percent, range is from 1 to 1000. 0 means undefined.
-     */
-    private int mViewportMinimumScale = 0;
-
-    /*
-     * scale in percent, range is from 1 to 1000. 0 means undefined.
-     */
-    private int mViewportMaximumScale = 0;
-
-    private boolean mViewportUserScalable = true;
-
-    /*
-     * range is from 70 to 400.
-     * 0 is a special value means device-dpi. The default scale factor will be
-     * always 100.
-     * -1 means undefined. The default scale factor will be
-     * WebView.DEFAULT_SCALE_PERCENT.
-     */
-    private int mViewportDensityDpi = -1;
-
-    private boolean mIsRestored = false;
-    private float mRestoredScale = 0;
-    private float mRestoredTextWrapScale = 0;
-    private int mRestoredX = 0;
-    private int mRestoredY = 0;
-
-    private MockGeolocation mMockGeolocation = new MockGeolocation(this);
-
-    private DeviceMotionAndOrientationManager mDeviceMotionAndOrientationManager =
-            new DeviceMotionAndOrientationManager(this);
-    private DeviceMotionService mDeviceMotionService;
-    private DeviceOrientationService mDeviceOrientationService;
-
-    private int mLowMemoryUsageThresholdMb;
-    private int mHighMemoryUsageThresholdMb;
-    private int mHighUsageDeltaMb;
-
-    private int mChromeCanFocusDirection;
-    private int mTextSelectionChangeReason = TextSelectionData.REASON_UNKNOWN;
-
-    // Used to determine if we should monitor the WebCore thread for responsiveness.
-    // If it "hangs", for example a web page enters a while(true) loop, we will
-    // prompt the user with a dialog allowing them to terminate the process.
-    private static boolean sShouldMonitorWebCoreThread;
-
-    // The thread name used to identify the WebCore thread and for use in
-    // debugging other classes that require operation within the WebCore thread.
-    /* package */ static final String THREAD_NAME = "WebViewCoreThread";
-
-    public WebViewCore(Context context, WebViewClassic w, CallbackProxy proxy,
-            Map<String, Object> javascriptInterfaces) {
-        // No need to assign this in the WebCore thread.
-        mCallbackProxy = proxy;
-        mWebViewClassic = w;
-        mJavascriptInterfaces = javascriptInterfaces;
-        // This context object is used to initialize the WebViewCore during
-        // subwindow creation.
-        mContext = context;
-
-        // We need to wait for the initial thread creation before sending
-        // a message to the WebCore thread.
-        // XXX: This is the only time the UI thread will wait for the WebCore
-        // thread!
-        synchronized (WebViewCore.class) {
-            if (sWebCoreHandler == null) {
-                // Create a global thread and start it.
-                Thread t = new Thread(new WebCoreThread());
-                t.setName(THREAD_NAME);
-                t.start();
-                try {
-                    WebViewCore.class.wait();
-                } catch (InterruptedException e) {
-                    Log.e(LOGTAG, "Caught exception while waiting for thread " +
-                           "creation.");
-                    Log.e(LOGTAG, Log.getStackTraceString(e));
-                }
-
-                if (sShouldMonitorWebCoreThread) {
-                    // Start the singleton watchdog which will monitor the WebCore thread
-                    // to verify it's still processing messages. Note that this is the only
-                    // time we need to check the value as all the other public methods on
-                    // the WebCoreThreadWatchdog are no-ops if start() is not called.
-                    WebCoreThreadWatchdog.start(sWebCoreHandler);
-                }
-            }
-            // Make sure the Watchdog is aware of this new WebView.
-            WebCoreThreadWatchdog.registerWebView(w);
-        }
-        // Create an EventHub to handle messages before and after the thread is
-        // ready.
-        mEventHub = new EventHub();
-        // Create a WebSettings object for maintaining all settings
-        mSettings = new WebSettingsClassic(mContext, mWebViewClassic);
-        // The WebIconDatabase needs to be initialized within the UI thread so
-        // just request the instance here.
-        WebIconDatabase.getInstance();
-        // Create the WebStorageClassic singleton and the UI handler
-        WebStorageClassic.getInstance().createUIHandler();
-        // Create the UI handler for GeolocationPermissions
-        GeolocationPermissionsClassic.getInstance().createUIHandler();
-
-        // Get the memory class of the current device. V8 will use these values
-        // to GC more effectively.
-        ActivityManager manager = (ActivityManager) mContext.getSystemService(
-                Context.ACTIVITY_SERVICE);
-        ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
-        manager.getMemoryInfo(memInfo);
-
-        // Allow us to use up to our memory class value before V8's GC kicks in.
-        // These values have been determined by experimentation.
-        mLowMemoryUsageThresholdMb = manager.getLargeMemoryClass();
-        mHighMemoryUsageThresholdMb = (int) (mLowMemoryUsageThresholdMb * 1.5);
-        // Avoid constant V8 GC when memory usage equals to working set estimate.
-        mHighUsageDeltaMb = mLowMemoryUsageThresholdMb / 32;
-
-        // Send a message to initialize the WebViewCore.
-        Message init = sWebCoreHandler.obtainMessage(
-                WebCoreThread.INITIALIZE, this);
-        sWebCoreHandler.sendMessage(init);
-    }
-
-    /* Initialize private data within the WebCore thread.
-     */
-    private void initialize() {
-        /* Initialize our private BrowserFrame class to handle all
-         * frame-related functions. We need to create a new view which
-         * in turn creates a C level FrameView and attaches it to the frame.
-         */
-        mBrowserFrame = new BrowserFrame(mContext, this, mCallbackProxy,
-                mSettings, mJavascriptInterfaces);
-        mJavascriptInterfaces = null;
-        // Sync the native settings and also create the WebCore thread handler.
-        mSettings.syncSettingsAndCreateHandler(mBrowserFrame);
-        // Create the handler and transfer messages for the IconDatabase
-        WebIconDatabaseClassic.getInstance().createHandler();
-        // Create the handler for WebStorageClassic
-        WebStorageClassic.getInstance().createHandler();
-        // Create the handler for GeolocationPermissions.
-        GeolocationPermissionsClassic.getInstance().createHandler();
-        // The transferMessages call will transfer all pending messages to the
-        // WebCore thread handler.
-        mEventHub.transferMessages();
-
-        // Send a message back to WebView to tell it that we have set up the
-        // WebCore thread.
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.WEBCORE_INITIALIZED_MSG_ID,
-                    mNativeClass, 0).sendToTarget();
-        }
-
-    }
-
-    /* Handle the initialization of WebViewCore during subwindow creation. This
-     * method is called from the WebCore thread but it is called before the
-     * INITIALIZE message can be handled.
-     */
-    /* package */ void initializeSubwindow() {
-        // Go ahead and initialize the core components.
-        initialize();
-        // Remove the INITIALIZE method so we don't try to initialize twice.
-        sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this);
-    }
-
-    /* Get the BrowserFrame component. This is used for subwindow creation and
-     * is called only from BrowserFrame in the WebCore thread. */
-    /* package */ synchronized BrowserFrame getBrowserFrame() {
-        return mBrowserFrame;
-    }
-
-    public WebKitCallbacks getInputDispatcherCallbacks() {
-        return mEventHub;
-    }
-
-    //-------------------------------------------------------------------------
-    // Common methods
-    //-------------------------------------------------------------------------
-
-    /**
-     * Causes all timers to pause. This applies to all WebViews in the current
-     * app process.
-     */
-    public static void pauseTimers() {
-        if (BrowserFrame.sJavaBridge == null) {
-            throw new IllegalStateException(
-                    "No WebView has been created in this process!");
-        }
-        BrowserFrame.sJavaBridge.pause();
-    }
-
-    /**
-     * Resume all timers. This applies to all WebViews in the current process.
-     */
-    public static void resumeTimers() {
-        if (BrowserFrame.sJavaBridge == null) {
-            throw new IllegalStateException(
-                    "No WebView has been created in this process!");
-        }
-        BrowserFrame.sJavaBridge.resume();
-    }
-
-    public WebSettingsClassic getSettings() {
-        return mSettings;
-    }
-
-    /*
-     * Given mimeType, check whether it's supported in Android media framework.
-     * mimeType could be such as "audio/ogg" and "video/mp4".
-     */
-    /* package */ static boolean isSupportedMediaMimeType(String mimeType) {
-        int fileType = MediaFile.getFileTypeForMimeType(mimeType);
-        return MediaFile.isAudioFileType(fileType)
-            || MediaFile.isVideoFileType(fileType)
-            || MediaFile.isPlayListFileType(fileType)
-            // The following is not in Media framework, but it's supported.
-            || (mimeType != null && mimeType.startsWith("video/m4v"));
-    }
-
-    /**
-     * Add an error message to the client's console.
-     * @param message The message to add
-     * @param lineNumber the line on which the error occurred
-     * @param sourceID the filename of the source that caused the error.
-     * @param msgLevel the log level of this message. This is a value casted to int
-     *     from WebCore::MessageLevel in WebCore/page/Console.h.
-     */
-    protected void addMessageToConsole(String message, int lineNumber, String sourceID,
-            int msgLevel) {
-        mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID, msgLevel);
-    }
-
-    /**
-     * Invoke a javascript alert.
-     * @param message The message displayed in the alert.
-     */
-    protected void jsAlert(String url, String message) {
-        mCallbackProxy.onJsAlert(url, message);
-    }
-
-    /**
-     * Called by JNI when the focus node changed.
-     */
-    private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) {
-        if (mWebViewClassic == null) return;
-        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED,
-                nodePointer, 0, hitTest).sendToTarget();
-    }
-
-    /**
-     * Called by JNI to advance focus to the next view.
-     */
-    private void chromeTakeFocus(int webkitDirection) {
-        if (mWebViewClassic == null) return;
-        Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
-                WebViewClassic.TAKE_FOCUS);
-        m.arg1 = mapDirection(webkitDirection);
-        m.sendToTarget();
-    }
-
-    /**
-     * Called by JNI to see if we can take focus in the given direction.
-     */
-    private boolean chromeCanTakeFocus(int webkitDirection) {
-        int direction = mapDirection(webkitDirection);
-        return direction == mChromeCanFocusDirection && direction != 0;
-    }
-
-    /**
-     * Maps a Webkit focus direction to a framework one
-     */
-    private int mapDirection(int webkitDirection) {
-        /*
-         * This is WebKit's FocusDirection enum (from FocusDirection.h)
-        enum FocusDirection {
-            FocusDirectionNone = 0,
-            FocusDirectionForward,
-            FocusDirectionBackward,
-            FocusDirectionUp,
-            FocusDirectionDown,
-            FocusDirectionLeft,
-            FocusDirectionRight
-        };
-         */
-        switch (webkitDirection) {
-        case 1:
-            return View.FOCUS_FORWARD;
-        case 2:
-            return View.FOCUS_BACKWARD;
-        case 3:
-            return View.FOCUS_UP;
-        case 4:
-            return View.FOCUS_DOWN;
-        case 5:
-            return View.FOCUS_LEFT;
-        case 6:
-            return View.FOCUS_RIGHT;
-        }
-        return 0;
-    }
-
-    /**
-     * Called by JNI.  Open a file chooser to upload a file.
-     * @param acceptType The value of the 'accept' attribute of the
-     *         input tag associated with this file picker.
-     * @param capture The value of the 'capture' attribute of the
-     *         input tag associated with this file picker.
-     * @return String version of the URI.
-     */
-    private String openFileChooser(String acceptType, String capture) {
-        Uri uri = mCallbackProxy.openFileChooser(acceptType, capture);
-        if (uri != null) {
-            String filePath = "";
-            // Note - querying for MediaStore.Images.Media.DATA
-            // seems to work for all content URIs, not just images
-            Cursor cursor = mContext.getContentResolver().query(
-                    uri,
-                    new String[] { MediaStore.Images.Media.DATA },
-                    null, null, null);
-            if (cursor != null) {
-                try {
-                    if (cursor.moveToNext()) {
-                        filePath = cursor.getString(0);
-                    }
-                } finally {
-                    cursor.close();
-                }
-            } else {
-                filePath = uri.getLastPathSegment();
-            }
-            String uriString = uri.toString();
-            BrowserFrame.sJavaBridge.storeFilePathForContentUri(filePath, uriString);
-            return uriString;
-        }
-        return "";
-    }
-
-    /**
-     * Notify the embedding application that the origin has exceeded it's database quota.
-     * @param url The URL that caused the overflow.
-     * @param databaseIdentifier The identifier of the database.
-     * @param quota The current quota for the origin.
-     * @param estimatedDatabaseSize The estimated size of the database.
-     */
-    protected void exceededDatabaseQuota(String url,
-                                         String databaseIdentifier,
-                                         long quota,
-                                         long estimatedDatabaseSize) {
-        // Inform the callback proxy of the quota overflow. Send an object
-        // that encapsulates a call to the nativeSetDatabaseQuota method to
-        // awaken the sleeping webcore thread when a decision from the
-        // client to allow or deny quota is available.
-        mCallbackProxy.onExceededDatabaseQuota(url, databaseIdentifier,
-                quota, estimatedDatabaseSize, getUsedQuota(),
-                new WebStorage.QuotaUpdater() {
-                        @Override
-                        public void updateQuota(long newQuota) {
-                            nativeSetNewStorageLimit(mNativeClass, newQuota);
-                        }
-                });
-    }
-
-    /**
-     * Notify the embedding application that the appcache has reached or exceeded its maximum
-     * allowed storage size.
-     *
-     * @param requiredStorage is the amount of storage, in bytes, that would be
-     * needed in order for the last appcache operation to succeed.
-     * @param maxSize maximum allowed Application Cache database size, in bytes.
-     */
-    protected void reachedMaxAppCacheSize(long requiredStorage, long maxSize) {
-        mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, maxSize,
-                new WebStorage.QuotaUpdater() {
-                    @Override
-                    public void updateQuota(long newQuota) {
-                        nativeSetNewStorageLimit(mNativeClass, newQuota);
-                    }
-                });
-    }
-
-    protected void populateVisitedLinks() {
-        ValueCallback callback = new ValueCallback<String[]>() {
-            @Override
-            public void onReceiveValue(String[] value) {
-                sendMessage(EventHub.POPULATE_VISITED_LINKS, (Object)value);
-            }
-        };
-        mCallbackProxy.getVisitedHistory(callback);
-    }
-
-    /**
-     * Shows a prompt to ask the user to set the Geolocation permission state
-     * for the given origin.
-     * @param origin The origin for which Geolocation permissions are
-     *     requested.
-     */
-    protected void geolocationPermissionsShowPrompt(String origin) {
-        mCallbackProxy.onGeolocationPermissionsShowPrompt(origin,
-                new GeolocationPermissions.Callback() {
-            @Override
-            public void invoke(String origin, boolean allow, boolean remember) {
-                GeolocationPermissionsData data = new GeolocationPermissionsData();
-                data.mOrigin = origin;
-                data.mAllow = allow;
-                data.mRemember = remember;
-                // Marshall to WebCore thread.
-                sendMessage(EventHub.GEOLOCATION_PERMISSIONS_PROVIDE, data);
-            }
-        });
-    }
-
-    /**
-     * Hides the Geolocation permissions prompt.
-     */
-    protected void geolocationPermissionsHidePrompt() {
-        mCallbackProxy.onGeolocationPermissionsHidePrompt();
-    }
-
-    /**
-     * Invoke a javascript confirm dialog.
-     * @param message The message displayed in the dialog.
-     * @return True if the user confirmed or false if the user cancelled.
-     */
-    protected boolean jsConfirm(String url, String message) {
-        return mCallbackProxy.onJsConfirm(url, message);
-    }
-
-    /**
-     * Invoke a javascript prompt dialog.
-     * @param message The message to be displayed in the dialog.
-     * @param defaultValue The default value in the prompt input.
-     * @return The input from the user or null to indicate the user cancelled
-     *         the dialog.
-     */
-    protected String jsPrompt(String url, String message, String defaultValue) {
-        return mCallbackProxy.onJsPrompt(url, message, defaultValue);
-    }
-
-    /**
-     * Invoke a javascript before unload dialog.
-     * @param url The url that is requesting the dialog.
-     * @param message The message displayed in the dialog.
-     * @return True if the user confirmed or false if the user cancelled. False
-     *         will cancel the navigation.
-     */
-    protected boolean jsUnload(String url, String message) {
-        return mCallbackProxy.onJsBeforeUnload(url, message);
-    }
-
-    /**
-     *
-     * Callback to notify that a JavaScript execution timeout has occured.
-     * @return True if the JavaScript execution should be interrupted. False
-     *         will continue the execution.
-     */
-    protected boolean jsInterrupt() {
-        return mCallbackProxy.onJsTimeout();
-    }
-
-    /**
-     * Notify the webview that we want to exit the video fullscreen.
-     * This is called through JNI by webcore.
-     */
-    protected void exitFullscreenVideo() {
-        if (mWebViewClassic == null) return;
-        Message message = Message.obtain(mWebViewClassic.mPrivateHandler,
-                       WebViewClassic.EXIT_FULLSCREEN_VIDEO);
-        message.sendToTarget();
-    }
-
-    /**
-     * Clear the picture set. To be called only on the WebCore thread.
-     */
-    /* package */ void clearContent() {
-        nativeClearContent(mNativeClass);
-    }
-
-    //-------------------------------------------------------------------------
-    // JNI methods
-    //-------------------------------------------------------------------------
-
-    static native String nativeFindAddress(String addr, boolean caseInsensitive);
-
-    /**
-     * Empty the picture set.
-     */
-    private native void nativeClearContent(int nativeClass);
-
-    private native void nativeContentInvalidateAll(int nativeClass);
-
-    /**
-     * Redraw a portion of the picture set. The Point wh returns the
-     * width and height of the overall picture.
-     */
-    private native int nativeRecordContent(int nativeClass, Point wh);
-
-    /**
-     * Notify webkit that animations have begun (on the hardware accelerated content)
-     */
-    private native void nativeNotifyAnimationStarted(int nativeClass);
-
-    private native boolean nativeKey(int nativeClass, int keyCode,
-            int unichar, int repeatCount, boolean isShift, boolean isAlt,
-            boolean isSym, boolean isDown);
-
-    private native void nativeSendListBoxChoices(int nativeClass,
-            boolean[] choices, int size);
-
-    private native void nativeSendListBoxChoice(int nativeClass, int choice);
-
-    private native void nativeCloseIdleConnections(int nativeClass);
-
-    /*  Tell webkit what its width and height are, for the purposes
-        of layout/line-breaking. These coordinates are in document space,
-        which is the same as View coords unless we have zoomed the document
-        (see nativeSetZoom).
-        textWrapWidth is used by layout to wrap column around. If viewport uses
-        fixed size, textWrapWidth can be different from width with zooming.
-        should this be called nativeSetViewPortSize?
-    */
-    private native void nativeSetSize(int nativeClass, int width, int height,
-            int textWrapWidth, float scale, int screenWidth, int screenHeight,
-            int anchorX, int anchorY, boolean ignoreHeight);
-
-    private native int nativeGetContentMinPrefWidth(int nativeClass);
-
-    // Start: functions that deal with text editing
-    private native void nativeReplaceTextfieldText(
-            int nativeClass, int oldStart, int oldEnd, String replace,
-            int newStart, int newEnd, int textGeneration);
-
-    private native void passToJs(int nativeClass,
-            int gen, String currentText, int keyCode, int keyValue,
-            boolean down, boolean cap, boolean fn, boolean sym);
-
-    private native void nativeSetFocusControllerActive(int nativeClass,
-            boolean active);
-
-    private native void nativeSaveDocumentState(int nativeClass);
-
-    private native void nativeMoveMouse(int nativeClass, int x, int y);
-
-    private native String nativeRetrieveHref(int nativeClass, int x, int y);
-    private native String nativeRetrieveAnchorText(int nativeClass,
-            int x, int y);
-    private native String nativeRetrieveImageSource(int nativeClass,
-            int x, int y);
-    private native boolean nativeMouseClick(int nativeClass);
-
-    private native int nativeHandleTouchEvent(int nativeClass, int action,
-            int[] idArray, int[] xArray, int[] yArray, int count,
-            int actionIndex, int metaState);
-
-    private native void nativeSetBackgroundColor(int nativeClass, int color);
-
-    private native void nativeDumpDomTree(int nativeClass, boolean useFile);
-
-    private native void nativeDumpRenderTree(int nativeClass, boolean useFile);
-
-    private native void nativeSetJsFlags(int nativeClass, String flags);
-
-    /**
-     *  Delete text from start to end in the focused textfield. If there is no
-     *  focus, or if start == end, silently fail.  If start and end are out of
-     *  order, swap them.
-     * @param  nativeClass Pointer to the C++ WebViewCore object mNativeClass
-     * @param  start   Beginning of selection to delete.
-     * @param  end     End of selection to delete.
-     * @param  textGeneration Text generation number when delete was pressed.
-     */
-    private native void nativeDeleteSelection(int nativeClass, int start,
-            int end, int textGeneration);
-
-    /**
-     *  Set the selection to (start, end) in the focused textfield. If start and
-     *  end are out of order, swap them.
-     * @param  nativeClass Pointer to the C++ WebViewCore object mNativeClass
-     * @param  start   Beginning of selection.
-     * @param  end     End of selection.
-     */
-    private native void nativeSetSelection(int nativeClass, int start, int end);
-
-    // Register a scheme to be treated as local scheme so that it can access
-    // local asset files for resources
-    private native void nativeRegisterURLSchemeAsLocal(int nativeClass,
-            String scheme);
-
-    /*
-     * Inform webcore that the user has decided whether to allow or deny new
-     * quota for the current origin or more space for the app cache, and that
-     * the main thread should wake up now.
-     * @param limit Is the new quota for an origin or new app cache max size.
-     */
-    private native void nativeSetNewStorageLimit(int nativeClass, long limit);
-
-    /**
-     * Provide WebCore with a Geolocation permission state for the specified
-     * origin.
-     * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
-     * @param origin The origin for which Geolocation permissions are provided.
-     * @param allow Whether Geolocation permissions are allowed.
-     * @param remember Whether this decision should be remembered beyond the
-     *     life of the current page.
-     */
-    private native void nativeGeolocationPermissionsProvide(int nativeClass,
-            String origin, boolean allow, boolean remember);
-
-    /**
-     * Provide WebCore with the previously visted links from the history database
-     * @param nativeClass TODO
-     */
-    private native void nativeProvideVisitedHistory(int nativeClass,
-            String[] history);
-
-    /**
-     * Modifies the current selection.
-     *
-     * Note: Accessibility support.
-     * @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
-     * @param direction The direction in which to alter the selection.
-     * @param granularity The granularity of the selection modification.
-     *
-     * @return The selection string.
-     */
-    private native String nativeModifySelection(int nativeClass, int direction,
-            int granularity);
-
-    // EventHub for processing messages
-    private final EventHub mEventHub;
-    // WebCore thread handler
-    private static Handler sWebCoreHandler;
-    // Class for providing Handler creation inside the WebCore thread.
-    private static class WebCoreThread implements Runnable {
-        // Message id for initializing a new WebViewCore.
-        private static final int INITIALIZE = 0;
-        private static final int REDUCE_PRIORITY = 1;
-        private static final int RESUME_PRIORITY = 2;
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            Assert.assertNull(sWebCoreHandler);
-            synchronized (WebViewCore.class) {
-                sWebCoreHandler = new Handler() {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        switch (msg.what) {
-                            case INITIALIZE:
-                                WebViewCore core = (WebViewCore) msg.obj;
-                                core.initialize();
-                                break;
-
-                            case REDUCE_PRIORITY:
-                                // 3 is an adjustable number.
-                                Process.setThreadPriority(
-                                        Process.THREAD_PRIORITY_DEFAULT + 3 *
-                                        Process.THREAD_PRIORITY_LESS_FAVORABLE);
-                                break;
-
-                            case RESUME_PRIORITY:
-                                Process.setThreadPriority(
-                                        Process.THREAD_PRIORITY_DEFAULT);
-                                break;
-
-                            case EventHub.ADD_PACKAGE_NAME:
-                                if (BrowserFrame.sJavaBridge == null) {
-                                    throw new IllegalStateException(
-                                            "No WebView has been created in this process!");
-                                }
-                                BrowserFrame.sJavaBridge.addPackageName((String) msg.obj);
-                                break;
-
-                            case EventHub.REMOVE_PACKAGE_NAME:
-                                if (BrowserFrame.sJavaBridge == null) {
-                                    throw new IllegalStateException(
-                                            "No WebView has been created in this process!");
-                                }
-                                BrowserFrame.sJavaBridge.removePackageName((String) msg.obj);
-                                break;
-
-                            case EventHub.PROXY_CHANGED:
-                                if (BrowserFrame.sJavaBridge == null) {
-                                    throw new IllegalStateException(
-                                            "No WebView has been created in this process!");
-                                }
-                                BrowserFrame.sJavaBridge.updateProxy((ProxyProperties)msg.obj);
-                                break;
-
-                            case EventHub.HEARTBEAT:
-                                // Ping back the watchdog to let it know we're still processing
-                                // messages.
-                                Message m = (Message)msg.obj;
-                                m.sendToTarget();
-                                break;
-                            case EventHub.TRUST_STORAGE_UPDATED:
-                                // post a task to network thread for updating trust manager
-                                nativeCertTrustChanged();
-                                CertificateChainValidator.handleTrustStorageUpdate();
-                                break;
-                        }
-                    }
-                };
-                WebViewCore.class.notify();
-            }
-            Looper.loop();
-        }
-    }
-
-    static class BaseUrlData {
-        String mBaseUrl;
-        String mData;
-        String mMimeType;
-        String mEncoding;
-        String mHistoryUrl;
-    }
-
-    static class JSInterfaceData {
-        Object mObject;
-        String mInterfaceName;
-        boolean mRequireAnnotation;
-    }
-
-    static class JSKeyData {
-        String mCurrentText;
-        KeyEvent mEvent;
-    }
-
-    static class MotionUpData {
-        int mFrame;
-        int mNode;
-        Rect mBounds;
-        int mX;
-        int mY;
-    }
-
-    static class GetUrlData {
-        String mUrl;
-        Map<String, String> mExtraHeaders;
-    }
-
-    static class PostUrlData {
-        String mUrl;
-        byte[] mPostData;
-    }
-
-    static class ReplaceTextData {
-        String mReplace;
-        int mNewStart;
-        int mNewEnd;
-        int mTextGeneration;
-    }
-
-    static class TextSelectionData {
-        static final int REASON_UNKNOWN = 0;
-        static final int REASON_ACCESSIBILITY_INJECTOR = 1;
-        static final int REASON_SELECT_WORD = 2;
-        public TextSelectionData(int start, int end, int selectTextPtr) {
-            mStart = start;
-            mEnd = end;
-            mSelectTextPtr = selectTextPtr;
-        }
-        int mStart;
-        int mEnd;
-        int mSelectTextPtr;
-        int mSelectionReason = TextSelectionData.REASON_UNKNOWN;
-    }
-
-    static class TouchUpData {
-        int mMoveGeneration;
-        int mFrame;
-        int mNode;
-        int mX;
-        int mY;
-        int mNativeLayer;
-        Rect mNativeLayerRect = new Rect();
-    }
-
-    static class TouchHighlightData {
-        int mX;
-        int mY;
-        int mSlop;
-        int mNativeLayer;
-        Rect mNativeLayerRect;
-    }
-
-    static class WebKitHitTest {
-        String mLinkUrl;
-        String mIntentUrl;
-        String mAnchorText;
-        String mImageUrl;
-        String mAltDisplayString;
-        String mTitle;
-        Rect[] mTouchRects;
-        boolean mEditable;
-        int mTapHighlightColor = WebViewClassic.HIGHLIGHT_COLOR;
-        Rect[] mEnclosingParentRects;
-        boolean mHasFocus;
-
-        // These are the input values that produced this hit test
-        int mHitTestX;
-        int mHitTestY;
-        int mHitTestSlop;
-        boolean mHitTestMovedMouse;
-    }
-
-    static class AutoFillData {
-        public AutoFillData() {
-            mQueryId = WebTextView.FORM_NOT_AUTOFILLABLE;
-            mPreview = "";
-        }
-
-        public AutoFillData(int queryId, String preview) {
-            mQueryId = queryId;
-            mPreview = preview;
-        }
-
-        public int getQueryId() {
-            return mQueryId;
-        }
-
-        public String getPreviewString() {
-            return mPreview;
-        }
-
-        private int mQueryId;
-        private String mPreview;
-    }
-
-    static class TextFieldInitData {
-        public int mFieldPointer;
-        public String mText;
-        public int mType;
-        public boolean mIsSpellCheckEnabled;
-        public boolean mIsTextFieldNext;
-        public boolean mIsTextFieldPrev;
-        public boolean mIsAutoCompleteEnabled;
-        public String mName;
-        public String mLabel;
-        public int mMaxLength;
-        public Rect mContentBounds;
-        public int mNodeLayerId;
-        public Rect mClientRect;
-    }
-
-    // mAction of TouchEventData can be MotionEvent.getAction() which uses the
-    // last two bytes or one of the following values
-    static final int ACTION_LONGPRESS = 0x100;
-    static final int ACTION_DOUBLETAP = 0x200;
-
-    private static final int TOUCH_FLAG_HIT_HANDLER = 0x1;
-    private static final int TOUCH_FLAG_PREVENT_DEFAULT = 0x2;
-
-    static class TouchEventData {
-        int mAction;
-        int[] mIds;  // Ids of the touch points
-        Point[] mPoints;
-        Point[] mPointsInView;  // the point coordinates in view axis.
-        int mActionIndex;  // Associated pointer index for ACTION_POINTER_DOWN/UP
-        int mMetaState;
-        boolean mReprocess;
-        MotionEvent mMotionEvent;
-        int mNativeLayer;
-        Rect mNativeLayerRect = new Rect();
-        long mSequence;
-        boolean mNativeResult;
-    }
-
-    static class GeolocationPermissionsData {
-        String mOrigin;
-        boolean mAllow;
-        boolean mRemember;
-    }
-
-        static final String[] HandlerDebugString = {
-            "REVEAL_SELECTION", // 96
-            "", // 97
-            "", // = 98
-            "SCROLL_TEXT_INPUT", // = 99
-            "LOAD_URL", // = 100;
-            "STOP_LOADING", // = 101;
-            "RELOAD", // = 102;
-            "KEY_DOWN", // = 103;
-            "KEY_UP", // = 104;
-            "VIEW_SIZE_CHANGED", // = 105;
-            "GO_BACK_FORWARD", // = 106;
-            "SET_SCROLL_OFFSET", // = 107;
-            "RESTORE_STATE", // = 108;
-            "PAUSE_TIMERS", // = 109;
-            "RESUME_TIMERS", // = 110;
-            "CLEAR_CACHE", // = 111;
-            "CLEAR_HISTORY", // = 112;
-            "SET_SELECTION", // = 113;
-            "REPLACE_TEXT", // = 114;
-            "PASS_TO_JS", // = 115;
-            "SET_GLOBAL_BOUNDS", // = 116;
-            "", // = 117;
-            "CLICK", // = 118;
-            "SET_NETWORK_STATE", // = 119;
-            "DOC_HAS_IMAGES", // = 120;
-            "FAKE_CLICK", // = 121;
-            "DELETE_SELECTION", // = 122;
-            "LISTBOX_CHOICES", // = 123;
-            "SINGLE_LISTBOX_CHOICE", // = 124;
-            "MESSAGE_RELAY", // = 125;
-            "SET_BACKGROUND_COLOR", // = 126;
-            "SET_MOVE_FOCUS", // = 127
-            "SAVE_DOCUMENT_STATE", // = 128;
-            "129", // = 129;
-            "WEBKIT_DRAW", // = 130;
-            "131", // = 131;
-            "POST_URL", // = 132;
-            "", // = 133;
-            "CLEAR_CONTENT", // = 134;
-            "", // = 135;
-            "", // = 136;
-            "REQUEST_CURSOR_HREF", // = 137;
-            "ADD_JS_INTERFACE", // = 138;
-            "LOAD_DATA", // = 139;
-            "", // = 140;
-            "", // = 141;
-            "SET_ACTIVE", // = 142;
-            "ON_PAUSE",     // = 143
-            "ON_RESUME",    // = 144
-            "FREE_MEMORY",  // = 145
-            "VALID_NODE_BOUNDS", // = 146
-            "SAVE_WEBARCHIVE", // = 147
-            "WEBKIT_DRAW_LAYERS", // = 148;
-            "REMOVE_JS_INTERFACE", // = 149;
-        };
-
-    static class FindAllRequest {
-        public FindAllRequest(String text) {
-            mSearchText = text;
-            mMatchCount = -1;
-            mMatchIndex = -1;
-        }
-        public final String mSearchText;
-        public int mMatchCount;
-        public int mMatchIndex;
-    }
-
-    static class SaveViewStateRequest {
-        SaveViewStateRequest(OutputStream s, ValueCallback<Boolean> cb) {
-            mStream = s;
-            mCallback = cb;
-        }
-        public OutputStream mStream;
-        public ValueCallback<Boolean> mCallback;
-    }
-
-    /**
-     * @hide
-     */
-    public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
-        // Message Ids
-        static final int REVEAL_SELECTION = 96;
-        static final int SCROLL_TEXT_INPUT = 99;
-        static final int LOAD_URL = 100;
-        static final int STOP_LOADING = 101;
-        static final int RELOAD = 102;
-        static final int KEY_DOWN = 103;
-        static final int KEY_UP = 104;
-        static final int VIEW_SIZE_CHANGED = 105;
-        static final int GO_BACK_FORWARD = 106;
-        static final int SET_SCROLL_OFFSET = 107;
-        static final int RESTORE_STATE = 108;
-        static final int PAUSE_TIMERS = 109;
-        static final int RESUME_TIMERS = 110;
-        static final int CLEAR_CACHE = 111;
-        static final int CLEAR_HISTORY = 112;
-        static final int SET_SELECTION = 113;
-        static final int REPLACE_TEXT = 114;
-        static final int PASS_TO_JS = 115;
-        static final int SET_GLOBAL_BOUNDS = 116;
-        static final int SET_NETWORK_STATE = 119;
-        static final int DOC_HAS_IMAGES = 120;
-        static final int DELETE_SELECTION = 122;
-        static final int LISTBOX_CHOICES = 123;
-        static final int SINGLE_LISTBOX_CHOICE = 124;
-        public static final int MESSAGE_RELAY = 125;
-        static final int SET_BACKGROUND_COLOR = 126;
-        static final int SAVE_DOCUMENT_STATE = 128;
-        static final int DELETE_SURROUNDING_TEXT = 129;
-
-
-        static final int WEBKIT_DRAW = 130;
-        static final int POST_URL = 132;
-        static final int CLEAR_CONTENT = 134;
-
-        // UI nav messages
-        static final int SET_MOVE_MOUSE = 135;
-        static final int REQUEST_CURSOR_HREF = 137;
-        static final int ADD_JS_INTERFACE = 138;
-        static final int LOAD_DATA = 139;
-
-        // Used to tell the focus controller not to draw the blinking cursor,
-        // based on whether the WebView has focus and whether the WebView's
-        // cursor matches the webpage's focus.
-        static final int SET_ACTIVE = 142;
-
-        // lifecycle activities for just this DOM (unlike pauseTimers, which
-        // is global)
-        static final int ON_PAUSE = 143;
-        static final int ON_RESUME = 144;
-        static final int FREE_MEMORY = 145;
-
-        // Load and save web archives
-        static final int SAVE_WEBARCHIVE = 147;
-
-        static final int REMOVE_JS_INTERFACE = 149;
-
-        // Network-based messaging
-        static final int CLEAR_SSL_PREF_TABLE = 150;
-
-        // Test harness messages
-        static final int REQUEST_EXT_REPRESENTATION = 160;
-        static final int REQUEST_DOC_AS_TEXT = 161;
-
-        // debugging
-        static final int DUMP_DOMTREE = 170;
-        static final int DUMP_RENDERTREE = 171;
-
-        static final int SET_JS_FLAGS = 174;
-        static final int CONTENT_INVALIDATE_ALL = 175;
-        // Geolocation
-        static final int GEOLOCATION_PERMISSIONS_PROVIDE = 180;
-
-        static final int POPULATE_VISITED_LINKS = 181;
-
-        static final int HIDE_FULLSCREEN = 182;
-
-        static final int SET_NETWORK_TYPE = 183;
-
-        // navigator.isApplicationInstalled()
-        static final int ADD_PACKAGE_NAMES = 184;
-        static final int ADD_PACKAGE_NAME = 185;
-        static final int REMOVE_PACKAGE_NAME = 186;
-
-        // accessibility support
-        static final int MODIFY_SELECTION = 190;
-
-        static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191;
-
-        static final int AUTOFILL_FORM = 192;
-
-        static final int PROXY_CHANGED = 193;
-
-        static final int EXECUTE_JS = 194;
-
-        static final int PLUGIN_SURFACE_READY = 195;
-
-        static final int NOTIFY_ANIMATION_STARTED = 196;
-
-        static final int HEARTBEAT = 197;
-
-        static final int SCROLL_LAYER = 198;
-
-        // private message ids
-        private static final int DESTROY =     200;
-
-        // for cut & paste
-        static final int COPY_TEXT = 210;
-        static final int DELETE_TEXT = 211;
-        static final int INSERT_TEXT = 212;
-        static final int SELECT_TEXT = 213;
-        static final int SELECT_WORD_AT = 214;
-        static final int SELECT_ALL = 215;
-
-        // for updating state on trust storage change
-        static final int TRUST_STORAGE_UPDATED = 220;
-
-        // find-on-page controls
-        static final int FIND_ALL = 221;
-        static final int FIND_NEXT = 222;
-
-        // key was pressed (down and up)
-        static final int KEY_PRESS = 223;
-        static final int SET_INITIAL_FOCUS = 224;
-
-        static final int SAVE_VIEW_STATE = 225;
-        static final int SET_USE_MOCK_GEOLOCATION = 226;
-
-        // Private handler for WebCore messages.
-        private Handler mHandler;
-        // Message queue for containing messages before the WebCore thread is
-        // ready.
-        private LinkedList<Message> mMessages = new LinkedList<Message>();
-        // Flag for blocking messages. This is used during DESTROY to avoid
-        // posting more messages to the EventHub or to WebView's event handler.
-        private boolean mBlockMessages;
-        private boolean mDestroying;
-
-        private int mTid;
-        private int mSavedPriority;
-
-        /**
-         * Prevent other classes from creating an EventHub.
-         */
-        private EventHub() {}
-
-        private static final int FIRST_PACKAGE_MSG_ID = REVEAL_SELECTION;
-        private static final int LAST_PACKAGE_MSG_ID = REMOVE_JS_INTERFACE;
-
-        /**
-         * Transfer all messages to the newly created webcore thread handler.
-         */
-        private void transferMessages() {
-            mTid = Process.myTid();
-            mSavedPriority = Process.getThreadPriority(mTid);
-
-            mHandler = new Handler() {
-                @Override
-                public void handleMessage(Message msg) {
-                    if (DebugFlags.WEB_VIEW_CORE) {
-                        Log.v(LOGTAG, (msg.what < FIRST_PACKAGE_MSG_ID
-                                || msg.what > LAST_PACKAGE_MSG_ID
-                                ? Integer.toString(msg.what)
-                                : HandlerDebugString[msg.what
-                                        - FIRST_PACKAGE_MSG_ID])
-                                + " arg1=" + msg.arg1 + " arg2=" + msg.arg2
-                                + " obj=" + msg.obj);
-                    }
-                    switch (msg.what) {
-                    case PAUSE_TIMERS:
-                        mSavedPriority = Process.getThreadPriority(mTid);
-                        Process.setThreadPriority(mTid,
-                            Process.THREAD_PRIORITY_BACKGROUND);
-                        pauseTimers();
-                        if (mNativeClass != 0) {
-                            nativeCloseIdleConnections(mNativeClass);
-                        }
-                        return;
-
-                    case RESUME_TIMERS:
-                        Process.setThreadPriority(mTid, mSavedPriority);
-                        resumeTimers();
-                        return;
-                    }
-
-                    if (mWebViewClassic == null || mNativeClass == 0) {
-                        if (DebugFlags.WEB_VIEW_CORE) {
-                            Log.w(LOGTAG, "Rejecting message " + msg.what
-                                    + " because we are destroyed");
-                        }
-                        return;
-                    }
-                    if (mDestroying == true
-                            && msg.what != EventHub.DESTROY) {
-                        if (DebugFlags.WEB_VIEW_CORE) {
-                            Log.v(LOGTAG, "Rejecting message " + msg.what
-                                    + " because we are being destroyed");
-                        }
-                        return;
-                    }
-                    switch (msg.what) {
-                        case WEBKIT_DRAW:
-                            webkitDraw();
-                            break;
-
-                        case DESTROY:
-                            // Time to take down the world. Cancel all pending
-                            // loads and destroy the native view and frame.
-                            synchronized (WebViewCore.this) {
-                                mCallbackProxy.shutdown();
-                                // Wake up the WebCore thread just in case it is waiting for a
-                                // JavaScript dialog.
-                                synchronized (mCallbackProxy) {
-                                    mCallbackProxy.notify();
-                                }
-                                mBrowserFrame.destroy();
-                                mBrowserFrame = null;
-                                mSettings.onDestroyed();
-                                mNativeClass = 0;
-                                WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic);
-                                mWebViewClassic = null;
-                            }
-                            break;
-
-                        case REVEAL_SELECTION:
-                            nativeRevealSelection(mNativeClass);
-                            break;
-
-                        case SCROLL_TEXT_INPUT:
-                            float xPercent;
-                            if (msg.obj == null) {
-                                xPercent = 0f;
-                            } else {
-                                xPercent = ((Float) msg.obj).floatValue();
-                            }
-                            nativeScrollFocusedTextInput(mNativeClass, xPercent,
-                                    msg.arg2);
-                            break;
-
-                        case LOAD_URL: {
-                            CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
-                            GetUrlData param = (GetUrlData) msg.obj;
-                            loadUrl(param.mUrl, param.mExtraHeaders);
-                            break;
-                        }
-
-                        case POST_URL: {
-                            CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
-                            PostUrlData param = (PostUrlData) msg.obj;
-                            mBrowserFrame.postUrl(param.mUrl, param.mPostData);
-                            break;
-                        }
-                        case LOAD_DATA:
-                            CookieManagerClassic.getInstance().waitForCookieOperationsToComplete();
-                            BaseUrlData loadParams = (BaseUrlData) msg.obj;
-                            String baseUrl = loadParams.mBaseUrl;
-                            if (baseUrl != null) {
-                                int i = baseUrl.indexOf(':');
-                                if (i > 0) {
-                                    // In 1.0, WebView.loadDataWithBaseURL() could access local
-                                    // asset files using 'file' scheme URLs as long as the data is
-                                    // valid. Later versions of WebKit have tightened the
-                                    // restriction around when pages can access such local URLs.
-                                    // To maintain compatibility with 1.0, we register the scheme of
-                                    // the baseUrl to be considered local, as long as it is not
-                                    // http(s)/ftp(s)/about/javascript.
-                                    String scheme = baseUrl.substring(0, i);
-                                    if (!scheme.startsWith("http") &&
-                                            !scheme.startsWith("ftp") &&
-                                            !scheme.startsWith("about") &&
-                                            !scheme.startsWith("javascript")) {
-                                        nativeRegisterURLSchemeAsLocal(mNativeClass,
-                                                scheme);
-                                    }
-                                }
-                            }
-                            mBrowserFrame.loadData(baseUrl,
-                                    loadParams.mData,
-                                    loadParams.mMimeType,
-                                    loadParams.mEncoding,
-                                    loadParams.mHistoryUrl);
-                            nativeContentInvalidateAll(mNativeClass);
-                            break;
-
-                        case STOP_LOADING:
-                            // If the WebCore has committed the load, but not
-                            // finished the first layout yet, we need to set
-                            // first layout done to trigger the interpreted side sync
-                            // up with native side
-                            if (mBrowserFrame.committed()
-                                    && !mBrowserFrame.firstLayoutDone()) {
-                                mBrowserFrame.didFirstLayout();
-                            }
-                            // Do this after syncing up the layout state.
-                            stopLoading();
-                            break;
-
-                        case RELOAD:
-                            mBrowserFrame.reload(false);
-                            break;
-
-                        case KEY_DOWN:
-                            key((KeyEvent) msg.obj, msg.arg1, true);
-                            break;
-
-                        case KEY_UP:
-                            key((KeyEvent) msg.obj, msg.arg1, false);
-                            break;
-
-                        case KEY_PRESS:
-                            keyPress(msg.arg1);
-                            break;
-
-                        case VIEW_SIZE_CHANGED: {
-                            viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
-                            break;
-                        }
-                        case SET_SCROLL_OFFSET:
-                            // note: these are in document coordinates
-                            // (inv-zoom)
-                            Point pt = (Point) msg.obj;
-                            nativeSetScrollOffset(mNativeClass,
-                                    msg.arg1 == 1, pt.x, pt.y);
-                            break;
-
-                        case SET_GLOBAL_BOUNDS:
-                            Rect r = (Rect) msg.obj;
-                            nativeSetGlobalBounds(mNativeClass, r.left, r.top,
-                                r.width(), r.height());
-                            break;
-
-                        case GO_BACK_FORWARD:
-                            // If it is a standard load and the load is not
-                            // committed yet, we interpret BACK as RELOAD
-                            if (!mBrowserFrame.committed() && msg.arg1 == -1 &&
-                                    (mBrowserFrame.loadType() ==
-                                    BrowserFrame.FRAME_LOADTYPE_STANDARD)) {
-                                mBrowserFrame.reload(true);
-                            } else {
-                                mBrowserFrame.goBackOrForward(msg.arg1);
-                            }
-                            break;
-
-                        case RESTORE_STATE:
-                            stopLoading();
-                            restoreState(msg.arg1);
-                            break;
-
-
-                        case ON_PAUSE:
-                            nativePause(mNativeClass);
-                            break;
-
-                        case ON_RESUME:
-                            nativeResume(mNativeClass);
-                            break;
-
-                        case FREE_MEMORY:
-                            clearCache(false);
-                            nativeFreeMemory(mNativeClass);
-                            break;
-
-                        case SET_NETWORK_STATE:
-                            if (BrowserFrame.sJavaBridge == null) {
-                                throw new IllegalStateException("No WebView " +
-                                        "has been created in this process!");
-                            }
-                            BrowserFrame.sJavaBridge
-                                    .setNetworkOnLine(msg.arg1 == 1);
-                            break;
-
-                        case SET_NETWORK_TYPE:
-                            if (BrowserFrame.sJavaBridge == null) {
-                                throw new IllegalStateException("No WebView " +
-                                        "has been created in this process!");
-                            }
-                            Map<String, String> map = (Map<String, String>) msg.obj;
-                            BrowserFrame.sJavaBridge
-                                    .setNetworkType(map.get("type"), map.get("subtype"));
-                            break;
-
-                        case CLEAR_CACHE:
-                            clearCache(msg.arg1 == 1);
-                            break;
-
-                        case CLEAR_HISTORY:
-                            mCallbackProxy.getBackForwardList().
-                                    close(mBrowserFrame.mNativeFrame);
-                            break;
-
-                        case REPLACE_TEXT:
-                            ReplaceTextData rep = (ReplaceTextData) msg.obj;
-                            nativeReplaceTextfieldText(mNativeClass, msg.arg1,
-                                    msg.arg2, rep.mReplace, rep.mNewStart,
-                                    rep.mNewEnd, rep.mTextGeneration);
-                            break;
-
-                        case PASS_TO_JS: {
-                            JSKeyData jsData = (JSKeyData) msg.obj;
-                            KeyEvent evt = jsData.mEvent;
-                            int keyCode = evt.getKeyCode();
-                            int keyValue = evt.getUnicodeChar();
-                            int generation = msg.arg1;
-                            passToJs(mNativeClass,
-                                    generation,
-                                    jsData.mCurrentText,
-                                    keyCode,
-                                    keyValue,
-                                    evt.isDown(), evt.isShiftPressed(),
-                                    evt.isAltPressed(), evt.isSymPressed());
-                            break;
-                        }
-
-                        case SAVE_DOCUMENT_STATE: {
-                            nativeSaveDocumentState(mNativeClass);
-                            break;
-                        }
-
-                        case CLEAR_SSL_PREF_TABLE:
-                            // FIXME: This will not work for connections currently in use, as
-                            // they cache the certificate responses. See http://b/5324235.
-                            SslCertLookupTable.getInstance().clear();
-                            nativeCloseIdleConnections(mNativeClass);
-                            break;
-
-                        case SET_ACTIVE:
-                            nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
-                            break;
-
-                        case ADD_JS_INTERFACE:
-                            JSInterfaceData jsData = (JSInterfaceData) msg.obj;
-                            mBrowserFrame.addJavascriptInterface(jsData.mObject,
-                                    jsData.mInterfaceName, jsData.mRequireAnnotation);
-                            break;
-
-                        case REMOVE_JS_INTERFACE:
-                            jsData = (JSInterfaceData) msg.obj;
-                            mBrowserFrame.removeJavascriptInterface(
-                                    jsData.mInterfaceName);
-                            break;
-
-                        case REQUEST_EXT_REPRESENTATION:
-                            mBrowserFrame.externalRepresentation(
-                                    (Message) msg.obj);
-                            break;
-
-                        case REQUEST_DOC_AS_TEXT:
-                            mBrowserFrame.documentAsText((Message) msg.obj);
-                            break;
-
-                        case SET_MOVE_MOUSE:
-                            nativeMoveMouse(mNativeClass, msg.arg1, msg.arg2);
-                            break;
-
-                        case REQUEST_CURSOR_HREF: {
-                            WebKitHitTest hit = performHitTest(msg.arg1, msg.arg2, 1, false);
-                            Message hrefMsg = (Message) msg.obj;
-                            Bundle data = hrefMsg.getData();
-                            data.putString(FocusNodeHref.URL,hit.mLinkUrl);
-                            data.putString(FocusNodeHref.TITLE, hit.mAnchorText);
-                            data.putString(FocusNodeHref.SRC, hit.mImageUrl);
-                            hrefMsg.sendToTarget();
-                            break;
-                        }
-
-                        case DOC_HAS_IMAGES:
-                            Message imageResult = (Message) msg.obj;
-                            imageResult.arg1 =
-                                    mBrowserFrame.documentHasImages() ? 1 : 0;
-                            imageResult.sendToTarget();
-                            break;
-
-                        case DELETE_SELECTION:
-                            TextSelectionData deleteSelectionData
-                                    = (TextSelectionData) msg.obj;
-                            nativeDeleteSelection(mNativeClass,
-                                    deleteSelectionData.mStart, deleteSelectionData.mEnd, msg.arg1);
-                            break;
-
-                        case SET_SELECTION:
-                            nativeSetSelection(mNativeClass, msg.arg1, msg.arg2);
-                            break;
-
-                        case MODIFY_SELECTION:
-                            mTextSelectionChangeReason
-                                    = TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
-                            final SomeArgs args = (SomeArgs) msg.obj;
-                            final String modifiedSelectionString = nativeModifySelection(
-                                    mNativeClass, args.argi1, args.argi2);
-                            // If accessibility is on, the main thread may be
-                            // waiting for a response. Send on webcore thread.
-                            mWebViewClassic.handleSelectionChangedWebCoreThread(
-                                    modifiedSelectionString, args.argi3);
-                            args.recycle();
-                            mTextSelectionChangeReason
-                                    = TextSelectionData.REASON_UNKNOWN;
-                            break;
-
-                        case LISTBOX_CHOICES:
-                            SparseBooleanArray choices = (SparseBooleanArray)
-                                    msg.obj;
-                            int choicesSize = msg.arg1;
-                            boolean[] choicesArray = new boolean[choicesSize];
-                            for (int c = 0; c < choicesSize; c++) {
-                                choicesArray[c] = choices.get(c);
-                            }
-                            nativeSendListBoxChoices(mNativeClass,
-                                    choicesArray, choicesSize);
-                            break;
-
-                        case SINGLE_LISTBOX_CHOICE:
-                            nativeSendListBoxChoice(mNativeClass, msg.arg1);
-                            break;
-
-                        case SET_BACKGROUND_COLOR:
-                            nativeSetBackgroundColor(mNativeClass, msg.arg1);
-                            break;
-
-                        case DUMP_DOMTREE:
-                            nativeDumpDomTree(mNativeClass, msg.arg1 == 1);
-                            break;
-
-                        case DUMP_RENDERTREE:
-                            nativeDumpRenderTree(mNativeClass, msg.arg1 == 1);
-                            break;
-
-                        case SET_JS_FLAGS:
-                            nativeSetJsFlags(mNativeClass, (String)msg.obj);
-                            break;
-
-                        case CONTENT_INVALIDATE_ALL:
-                            nativeContentInvalidateAll(mNativeClass);
-                            break;
-
-                        case SAVE_WEBARCHIVE:
-                            WebViewClassic.SaveWebArchiveMessage saveMessage =
-                                (WebViewClassic.SaveWebArchiveMessage)msg.obj;
-                            saveMessage.mResultFile =
-                                saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname);
-                            mWebViewClassic.mPrivateHandler.obtainMessage(
-                                WebViewClassic.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget();
-                            break;
-
-                        case GEOLOCATION_PERMISSIONS_PROVIDE:
-                            GeolocationPermissionsData data =
-                                    (GeolocationPermissionsData) msg.obj;
-                            nativeGeolocationPermissionsProvide(mNativeClass,
-                                    data.mOrigin, data.mAllow, data.mRemember);
-                            break;
-
-                        case CLEAR_CONTENT:
-                            // Clear the view so that onDraw() will draw nothing
-                            // but white background
-                            // (See public method WebView.clearView)
-                            clearContent();
-                            break;
-
-                        case MESSAGE_RELAY:
-                            ((Message) msg.obj).sendToTarget();
-                            break;
-
-                        case POPULATE_VISITED_LINKS:
-                            nativeProvideVisitedHistory(mNativeClass, (String[])msg.obj);
-                            break;
-
-                        case HIDE_FULLSCREEN:
-                            nativeFullScreenPluginHidden(mNativeClass, msg.arg1);
-                            break;
-
-                        case PLUGIN_SURFACE_READY:
-                            nativePluginSurfaceReady(mNativeClass);
-                            break;
-
-                        case NOTIFY_ANIMATION_STARTED:
-                            nativeNotifyAnimationStarted(mNativeClass);
-                            break;
-
-                        case ADD_PACKAGE_NAMES:
-                            if (BrowserFrame.sJavaBridge == null) {
-                                throw new IllegalStateException("No WebView " +
-                                        "has been created in this process!");
-                            }
-                            BrowserFrame.sJavaBridge.addPackageNames(
-                                    (Set<String>) msg.obj);
-                            break;
-
-                        case SET_USE_MOCK_GEOLOCATION:
-                            setUseMockGeolocation();
-                            break;
-
-                        case SET_USE_MOCK_DEVICE_ORIENTATION:
-                            setUseMockDeviceOrientation();
-                            break;
-
-                        case AUTOFILL_FORM:
-                            nativeAutoFillForm(mNativeClass, msg.arg1);
-                            mWebViewClassic.mPrivateHandler.obtainMessage(
-                                    WebViewClassic.AUTOFILL_COMPLETE, null).sendToTarget();
-                            break;
-
-                        case EXECUTE_JS:
-                            if (msg.obj instanceof String) {
-                                if (DebugFlags.WEB_VIEW_CORE) {
-                                    Log.d(LOGTAG, "Executing JS : " + msg.obj);
-                                }
-                                mBrowserFrame.stringByEvaluatingJavaScriptFromString(
-                                        (String) msg.obj);
-                            }
-                            break;
-                        case SCROLL_LAYER:
-                            int nativeLayer = msg.arg1;
-                            Rect rect = (Rect) msg.obj;
-                            nativeScrollLayer(mNativeClass, nativeLayer,
-                                    rect);
-                            break;
-
-                        case DELETE_TEXT: {
-                            int[] handles = (int[]) msg.obj;
-                            nativeDeleteText(mNativeClass, handles[0],
-                                    handles[1], handles[2], handles[3]);
-                            break;
-                        }
-                        case COPY_TEXT: {
-                            int[] handles = (int[]) msg.obj;
-                            String copiedText = nativeGetText(mNativeClass,
-                                    handles[0], handles[1], handles[2],
-                                    handles[3]);
-                            if (copiedText != null) {
-                                mWebViewClassic.mPrivateHandler.obtainMessage(
-                                        WebViewClassic.COPY_TO_CLIPBOARD, copiedText)
-                                        .sendToTarget();
-                            }
-                            break;
-                        }
-                        case INSERT_TEXT:
-                            nativeInsertText(mNativeClass, (String) msg.obj);
-                            break;
-                        case SELECT_TEXT: {
-                            int handleId = (Integer) msg.obj;
-                            nativeSelectText(mNativeClass, handleId,
-                                    msg.arg1, msg.arg2);
-                            break;
-                        }
-                        case SELECT_WORD_AT: {
-                            mTextSelectionChangeReason
-                                    = TextSelectionData.REASON_SELECT_WORD;
-                            int x = msg.arg1;
-                            int y = msg.arg2;
-                            if (!nativeSelectWordAt(mNativeClass, x, y)) {
-                                mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_CARET_HANDLE)
-                                    .sendToTarget();
-                            }
-                            mTextSelectionChangeReason
-                                    = TextSelectionData.REASON_UNKNOWN;
-                            break;
-                        }
-                        case SELECT_ALL:
-                            nativeSelectAll(mNativeClass);
-                            break;
-                        case FIND_ALL: {
-                            FindAllRequest request = (FindAllRequest)msg.obj;
-                            if (request != null) {
-                                int matchCount = nativeFindAll(mNativeClass, request.mSearchText);
-                                int matchIndex = nativeFindNext(mNativeClass, true);
-                                synchronized (request) {
-                                    request.mMatchCount = matchCount;
-                                    request.mMatchIndex = matchIndex;
-                                    request.notify();
-                                }
-                            } else {
-                                nativeFindAll(mNativeClass, null);
-                            }
-                            Message.obtain(mWebViewClassic.mPrivateHandler,
-                                    WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
-                            break;
-                        }
-                        case FIND_NEXT: {
-                            FindAllRequest request = (FindAllRequest)msg.obj;
-                            int matchIndex = nativeFindNext(mNativeClass, msg.arg1 != 0);
-                            synchronized (request) {
-                                request.mMatchIndex = matchIndex;
-                            }
-                            Message.obtain(mWebViewClassic.mPrivateHandler,
-                                    WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
-                            break;
-                        }
-                        case SET_INITIAL_FOCUS:
-                            nativeSetInitialFocus(mNativeClass, msg.arg1);
-                            break;
-                        case SAVE_VIEW_STATE:
-                            SaveViewStateRequest request = (SaveViewStateRequest) msg.obj;
-                            saveViewState(request.mStream, request.mCallback);
-                            break;
-                    }
-                }
-
-            };
-            // Take all queued messages and resend them to the new handler.
-            synchronized (this) {
-                int size = mMessages.size();
-                for (int i = 0; i < size; i++) {
-                    mHandler.sendMessage(mMessages.get(i));
-                }
-                mMessages = null;
-            }
-        }
-
-        @Override
-        public Looper getWebKitLooper() {
-            return mHandler.getLooper();
-        }
-
-        @Override
-        public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher,
-                MotionEvent event, int eventType, int flags) {
-            if (mNativeClass == 0) {
-                return false;
-            }
-            switch (eventType) {
-                case WebViewInputDispatcher.EVENT_TYPE_HIT_TEST:
-                    int x = Math.round(event.getX());
-                    int y = Math.round(event.getY());
-                    WebKitHitTest hit = performHitTest(x, y,
-                            mWebViewClassic.getScaledNavSlop(), true);
-                    mWebViewClassic.mPrivateHandler.obtainMessage(
-                            WebViewClassic.HIT_TEST_RESULT, hit).sendToTarget();
-                    return false;
-
-                case WebViewInputDispatcher.EVENT_TYPE_CLICK:
-                    return nativeMouseClick(mNativeClass);
-
-                case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
-                    int count = event.getPointerCount();
-                    int[] idArray = new int[count];
-                    int[] xArray = new int[count];
-                    int[] yArray = new int[count];
-                    for (int i = 0; i < count; i++) {
-                        idArray[i] = event.getPointerId(i);
-                        xArray[i] = (int) event.getX(i);
-                        yArray[i] = (int) event.getY(i);
-                    }
-                    int touchFlags = nativeHandleTouchEvent(mNativeClass,
-                            event.getActionMasked(),
-                            idArray, xArray, yArray, count,
-                            event.getActionIndex(), event.getMetaState());
-                    if (touchFlags == 0
-                            && event.getActionMasked() != MotionEvent.ACTION_CANCEL
-                            && (flags & WebViewInputDispatcher.FLAG_PRIVATE) == 0) {
-                        dispatcher.skipWebkitForRemainingTouchStream();
-                    }
-                    return (touchFlags & TOUCH_FLAG_PREVENT_DEFAULT) > 0;
-                }
-
-                default:
-                    return false;
-            }
-        }
-
-        /**
-         * Send a message internally to the queue or to the handler
-         */
-        private synchronized void sendMessage(Message msg) {
-            if (mBlockMessages) {
-                return;
-            }
-            if (mMessages != null) {
-                mMessages.add(msg);
-            } else {
-                mHandler.sendMessage(msg);
-            }
-        }
-
-        private synchronized void removeMessages(int what) {
-            if (mBlockMessages) {
-                return;
-            }
-            if (what == EventHub.WEBKIT_DRAW) {
-                mDrawIsScheduled = false;
-            }
-            if (mMessages != null) {
-                Iterator<Message> iter = mMessages.iterator();
-                while (iter.hasNext()) {
-                    Message m = iter.next();
-                    if (m.what == what) {
-                        iter.remove();
-                    }
-                }
-            } else {
-                mHandler.removeMessages(what);
-            }
-        }
-
-        private synchronized void sendMessageDelayed(Message msg, long delay) {
-            if (mBlockMessages) {
-                return;
-            }
-            mHandler.sendMessageDelayed(msg, delay);
-        }
-
-        /**
-         * Send a message internally to the front of the queue.
-         */
-        private synchronized void sendMessageAtFrontOfQueue(Message msg) {
-            if (mBlockMessages) {
-                return;
-            }
-            if (mMessages != null) {
-                mMessages.add(0, msg);
-            } else {
-                mHandler.sendMessageAtFrontOfQueue(msg);
-            }
-        }
-
-        /**
-         * Remove all the messages.
-         */
-        private synchronized void removeMessages() {
-            // reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed
-            mDrawIsScheduled = false;
-            if (mMessages != null) {
-                mMessages.clear();
-            } else {
-                mHandler.removeCallbacksAndMessages(null);
-            }
-        }
-
-        /**
-         * Block sending messages to the EventHub.
-         */
-        private synchronized void blockMessages() {
-            mBlockMessages = true;
-        }
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods called by host activity (in the same thread)
-    //-------------------------------------------------------------------------
-
-    void stopLoading() {
-        if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "CORE stopLoading");
-        if (mBrowserFrame != null) {
-            mBrowserFrame.stopLoading();
-        }
-    }
-
-    //-------------------------------------------------------------------------
-    // Methods called by WebView
-    // If it refers to local variable, it needs synchronized().
-    // If it needs WebCore, it has to send message.
-    //-------------------------------------------------------------------------
-
-    /**
-     * @hide
-     */
-    public void sendMessage(Message msg) {
-        mEventHub.sendMessage(msg);
-    }
-
-    void sendMessages(ArrayList<Message> messages) {
-        synchronized (mEventHub) {
-            for (int i = 0; i < messages.size(); i++) {
-                mEventHub.sendMessage(messages.get(i));
-            }
-        }
-    }
-
-    void sendMessage(int what) {
-        mEventHub.sendMessage(Message.obtain(null, what));
-    }
-
-    void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
-        mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
-                null, what, arg1, arg2, obj));
-    }
-
-    void sendMessage(int what, Object obj) {
-        mEventHub.sendMessage(Message.obtain(null, what, obj));
-    }
-
-    void sendMessage(int what, int arg1) {
-        // just ignore the second argument (make it 0)
-        mEventHub.sendMessage(Message.obtain(null, what, arg1, 0));
-    }
-
-    void sendMessage(int what, int arg1, int arg2) {
-        mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2));
-    }
-
-    void sendMessage(int what, int arg1, Object obj) {
-        // just ignore the second argument (make it 0)
-        mEventHub.sendMessage(Message.obtain(null, what, arg1, 0, obj));
-    }
-
-    void sendMessage(int what, int arg1, int arg2, Object obj) {
-        mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj));
-    }
-
-    void sendMessageAtFrontOfQueue(int what, Object obj) {
-        mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
-                null, what, obj));
-    }
-
-    void sendMessageDelayed(int what, Object obj, long delay) {
-        mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay);
-    }
-
-    void removeMessages(int what) {
-        mEventHub.removeMessages(what);
-    }
-
-    void removeMessages() {
-        mEventHub.removeMessages();
-    }
-
-    /**
-     * Sends a DESTROY message to WebCore.
-     * Called from UI thread.
-     */
-    void destroy() {
-        synchronized (mEventHub) {
-            // send DESTROY to front of queue
-            // PAUSE/RESUME timers will still be processed even if they get handled later
-            mEventHub.mDestroying = true;
-            mEventHub.sendMessageAtFrontOfQueue(
-                    Message.obtain(null, EventHub.DESTROY));
-            mEventHub.blockMessages();
-        }
-    }
-
-    //-------------------------------------------------------------------------
-    // WebViewCore private methods
-    //-------------------------------------------------------------------------
-
-    private WebKitHitTest performHitTest(int x, int y, int slop, boolean moveMouse) {
-        WebKitHitTest hit = nativeHitTest(mNativeClass, x, y, slop, moveMouse);
-        hit.mHitTestX = x;
-        hit.mHitTestY = y;
-        hit.mHitTestSlop = slop;
-        hit.mHitTestMovedMouse = moveMouse;
-        return hit;
-    }
-
-    private void clearCache(boolean includeDiskFiles) {
-        mBrowserFrame.clearCache();
-    }
-
-    private void loadUrl(String url, Map<String, String> extraHeaders) {
-        if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, " CORE loadUrl " + url);
-        mBrowserFrame.loadUrl(url, extraHeaders);
-    }
-
-    private String saveWebArchive(String filename, boolean autoname) {
-        if (DebugFlags.WEB_VIEW_CORE) {
-            Log.v(LOGTAG, " CORE saveWebArchive " + filename + " " + autoname);
-        }
-        return mBrowserFrame.saveWebArchive(filename, autoname);
-    }
-
-    private void key(KeyEvent evt, int canTakeFocusDirection, boolean isDown) {
-        if (DebugFlags.WEB_VIEW_CORE) {
-            Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", "
-                    + evt);
-        }
-        mChromeCanFocusDirection = canTakeFocusDirection;
-        int keyCode = evt.getKeyCode();
-        int unicodeChar = evt.getUnicodeChar();
-
-        if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
-                && evt.getCharacters().length() > 0) {
-            // we should only receive individual complex characters
-            unicodeChar = evt.getCharacters().codePointAt(0);
-        }
-
-        boolean handled = nativeKey(mNativeClass, keyCode, unicodeChar, evt.getRepeatCount(),
-                evt.isShiftPressed(), evt.isAltPressed(),
-                evt.isSymPressed(), isDown);
-        mChromeCanFocusDirection = 0;
-        if (!handled && keyCode != KeyEvent.KEYCODE_ENTER) {
-            if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
-                    && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-                if (canTakeFocusDirection != 0 && isDown) {
-                    Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
-                            WebViewClassic.TAKE_FOCUS);
-                    m.arg1 = canTakeFocusDirection;
-                    m.sendToTarget();
-                }
-                return;
-            }
-            // bubble up the event handling
-            // but do not bubble up the ENTER key, which would open the search
-            // bar without any text.
-            mCallbackProxy.onUnhandledKeyEvent(evt);
-        }
-    }
-
-    private void keyPress(int unicodeChar) {
-        nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, true);
-        nativeKey(mNativeClass, 0, unicodeChar, 0, false, false, false, false);
-    }
-
-    // These values are used to avoid requesting a layout based on old values
-    private int mCurrentViewWidth = 0;
-    private int mCurrentViewHeight = 0;
-    private float mCurrentViewScale = 1.0f;
-
-    // notify webkit that our virtual view size changed size (after inv-zoom)
-    private void viewSizeChanged(WebViewClassic.ViewSizeData data) {
-        int w = data.mWidth;
-        int h = data.mHeight;
-        int textwrapWidth = data.mTextWrapWidth;
-        float scale = data.mScale;
-        if (DebugFlags.WEB_VIEW_CORE) {
-            Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h
-                    + "; textwrapWidth=" + textwrapWidth + "; scale=" + scale);
-        }
-        if (w == 0) {
-            Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
-            return;
-        }
-        int width = calculateWindowWidth(w);
-        int height = h;
-        if (width != w) {
-            float heightWidthRatio = data.mHeightWidthRatio;
-            float ratio = (heightWidthRatio > 0) ? heightWidthRatio : (float) h / w;
-            height = Math.round(ratio * width);
-        }
-        int screenHeight = data.mActualViewHeight > 0 ? data.mActualViewHeight : h;
-        nativeSetSize(mNativeClass, width, height, textwrapWidth, scale,
-                w, screenHeight, data.mAnchorX, data.mAnchorY, data.mIgnoreHeight);
-        // Remember the current width and height
-        boolean needInvalidate = (mCurrentViewWidth == 0);
-        mCurrentViewWidth = w;
-        mCurrentViewHeight = h;
-        mCurrentViewScale = scale;
-        if (needInvalidate) {
-            // ensure {@link #webkitDraw} is called as we were blocking in
-            // {@link #contentDraw} when mCurrentViewWidth is 0
-            if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "viewSizeChanged");
-            contentDraw();
-        }
-    }
-
-    // Calculate width to be used in webkit window.
-    private int calculateWindowWidth(int viewWidth) {
-        int width = viewWidth;
-        if (mSettings.getUseWideViewPort()) {
-            if (mViewportWidth == -1) {
-                // Fixed viewport width.
-                width = WebViewClassic.DEFAULT_VIEWPORT_WIDTH;
-            } else if (mViewportWidth > 0) {
-                // Use website specified or desired fixed viewport width.
-                width = mViewportWidth;
-            } else {
-                // For mobile web site.
-                width = Math.round(mWebViewClassic.getViewWidth() /
-                        mWebViewClassic.getDefaultZoomScale());
-            }
-        }
-        return width;
-    }
-
-    // Utility method for exceededDatabaseQuota callback. Computes the sum
-    // of WebSQL database quota for all origins.
-    private long getUsedQuota() {
-        WebStorageClassic webStorage = WebStorageClassic.getInstance();
-        Collection<WebStorage.Origin> origins = webStorage.getOriginsSync();
-
-        if (origins == null) {
-            return 0;
-        }
-        long usedQuota = 0;
-        for (WebStorage.Origin website : origins) {
-            usedQuota += website.getQuota();
-        }
-        return usedQuota;
-    }
-
-    // Used to avoid posting more than one draw message.
-    private boolean mDrawIsScheduled;
-
-    // Used to suspend drawing.
-    private boolean mDrawIsPaused;
-
-    // mInitialViewState is set by didFirstLayout() and then reset in the
-    // next webkitDraw after passing the state to the UI thread.
-    private ViewState mInitialViewState = null;
-    private boolean mFirstLayoutForNonStandardLoad;
-
-    static class ViewState {
-        float mMinScale;
-        float mMaxScale;
-        float mViewScale;
-        float mTextWrapScale;
-        float mDefaultScale;
-        int mScrollX;
-        int mScrollY;
-        boolean mMobileSite;
-        boolean mIsRestored;
-        boolean mShouldStartScrolledRight;
-    }
-
-    static class DrawData {
-        DrawData() {
-            mBaseLayer = 0;
-            mContentSize = new Point();
-        }
-        int mBaseLayer;
-        // view size that was used by webkit during the most recent layout
-        Point mViewSize;
-        Point mContentSize;
-        int mMinPrefWidth;
-        // only non-null if it is for the first picture set after the first layout
-        ViewState mViewState;
-        boolean mFirstLayoutForNonStandardLoad;
-    }
-
-    DrawData mLastDrawData = null;
-
-    private Object m_skipDrawFlagLock = new Object();
-    private boolean m_skipDrawFlag = false;
-    private boolean m_drawWasSkipped = false;
-
-    void pauseWebKitDraw() {
-        synchronized (m_skipDrawFlagLock) {
-            if (!m_skipDrawFlag) {
-                m_skipDrawFlag = true;
-            }
-        }
-    }
-
-    void resumeWebKitDraw() {
-        synchronized (m_skipDrawFlagLock) {
-            if (m_skipDrawFlag && m_drawWasSkipped) {
-                // a draw was dropped, send a retry
-                m_drawWasSkipped = false;
-                mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
-            }
-            m_skipDrawFlag = false;
-        }
-    }
-
-    private void webkitDraw() {
-        synchronized (m_skipDrawFlagLock) {
-            if (m_skipDrawFlag) {
-                m_drawWasSkipped = true;
-                return;
-            }
-        }
-
-        mDrawIsScheduled = false;
-        DrawData draw = new DrawData();
-        if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
-        draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mContentSize);
-        if (draw.mBaseLayer == 0) {
-            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 {
-                if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, webview paused");
-            }
-            return;
-        }
-        mLastDrawData = draw;
-        webkitDraw(draw);
-    }
-
-    private void webkitDraw(DrawData draw) {
-        if (mWebViewClassic != null) {
-            draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight);
-            if (mSettings.getUseWideViewPort()) {
-                draw.mMinPrefWidth = Math.max(
-                        mViewportWidth == -1 ? WebViewClassic.DEFAULT_VIEWPORT_WIDTH
-                                : (mViewportWidth == 0 ? mCurrentViewWidth
-                                        : mViewportWidth),
-                        nativeGetContentMinPrefWidth(mNativeClass));
-            }
-            if (mInitialViewState != null) {
-                draw.mViewState = mInitialViewState;
-                mInitialViewState = null;
-            }
-            if (mFirstLayoutForNonStandardLoad) {
-                draw.mFirstLayoutForNonStandardLoad = true;
-                mFirstLayoutForNonStandardLoad = false;
-            }
-            if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
-            pauseWebKitDraw();
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.NEW_PICTURE_MSG_ID, draw).sendToTarget();
-        }
-    }
-
-    private void saveViewState(OutputStream stream,
-            ValueCallback<Boolean> callback) {
-        // TODO: Create a native method to do this better without overloading
-        // the draw path (and fix saving <canvas>)
-        DrawData draw = new DrawData();
-        if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "saveViewState start");
-        draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mContentSize);
-        boolean result = false;
-        try {
-            result = ViewStateSerializer.serializeViewState(stream, draw);
-        } catch (Throwable t) {
-            Log.w(LOGTAG, "Failed to save view state", t);
-        }
-        callback.onReceiveValue(result);
-        if (draw.mBaseLayer != 0) {
-            if (mDrawIsScheduled) {
-                mDrawIsScheduled = false;
-                mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
-            }
-            mLastDrawData = draw;
-            webkitDraw(draw);
-        }
-    }
-
-    static void reducePriority() {
-        // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
-        sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
-        sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
-        sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
-                .obtainMessage(WebCoreThread.REDUCE_PRIORITY));
-    }
-
-    static void resumePriority() {
-        // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
-        sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
-        sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
-        sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
-                .obtainMessage(WebCoreThread.RESUME_PRIORITY));
-    }
-
-    static void sendStaticMessage(int messageType, Object argument) {
-        if (sWebCoreHandler == null)
-            return;
-
-        sWebCoreHandler.sendMessage(sWebCoreHandler.obtainMessage(messageType, argument));
-    }
-
-    static void pauseUpdatePicture(WebViewCore core) {
-        // Note: there is one possible failure mode. If pauseUpdatePicture() is
-        // called from UI thread while WEBKIT_DRAW is just pulled out of the
-        // queue in WebCore thread to be executed. Then update won't be blocked.
-        if (core != null) {
-            if (!core.getSettings().enableSmoothTransition()) return;
-
-            synchronized (core) {
-                if (core.mNativeClass == 0) {
-                    Log.w(LOGTAG, "Cannot pauseUpdatePicture, core destroyed or not initialized!");
-                    return;
-                }
-                core.mDrawIsPaused = true;
-            }
-        }
-
-    }
-
-    static void resumeUpdatePicture(WebViewCore core) {
-        if (core != null) {
-            // if mDrawIsPaused is true, ignore the setting, continue to resume
-            if (!core.mDrawIsPaused)
-                return;
-
-            synchronized (core) {
-                if (core.mNativeClass == 0) {
-                    Log.w(LOGTAG, "Cannot resumeUpdatePicture, core destroyed!");
-                    return;
-                }
-                core.mDrawIsPaused = false;
-                // always redraw on resume to reenable gif animations
-                core.mDrawIsScheduled = false;
-            }
-        }
-    }
-
-    static boolean isUpdatePicturePaused(WebViewCore core) {
-        return core != null ? core.mDrawIsPaused : false;
-    }
-
-    //////////////////////////////////////////////////////////////////////////
-
-    private void restoreState(int index) {
-        WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
-        int size = list.getSize();
-        for (int i = 0; i < size; i++) {
-            list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
-        }
-        mBrowserFrame.mLoadInitFromJava = true;
-        WebBackForwardListClassic.restoreIndex(mBrowserFrame.mNativeFrame, index);
-        mBrowserFrame.mLoadInitFromJava = false;
-    }
-
-    //-------------------------------------------------------------------------
-    // Implement abstract methods in WebViewCore, native WebKit callback part
-    //-------------------------------------------------------------------------
-
-    // called from JNI or WebView thread
-    /* package */ void contentDraw() {
-        synchronized (this) {
-            if (mWebViewClassic == null || mBrowserFrame == null) {
-                // We were destroyed
-                return;
-            }
-            // don't update the Picture until we have an initial width and finish
-            // the first layout
-            if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) {
-                return;
-            }
-            // only fire an event if this is our first request
-            if (mDrawIsScheduled) return;
-            mDrawIsScheduled = true;
-            mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
-        }
-    }
-
-    // called by JNI
-    private void contentScrollTo(int x, int y, boolean animate,
-            boolean onlyIfImeIsShowing) {
-        if (!mBrowserFrame.firstLayoutDone()) {
-            /*
-             * WebKit restore state will be called before didFirstLayout(),
-             * remember the position as it has to be applied after restoring
-             * zoom factor which is controlled by screenWidth.
-             */
-            mRestoredX = x;
-            mRestoredY = y;
-            return;
-        }
-        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) {
-                mEventHub.sendMessage(Message.obtain(null,
-                        EventHub.MESSAGE_RELAY, msg));
-            } else {
-                msg.sendToTarget();
-            }
-        }
-    }
-
-    // called by JNI
-    private void sendNotifyProgressFinished() {
-        contentDraw();
-    }
-
-    /*  Called by JNI. The coordinates are in doc coordinates, so they need to
-        be scaled before they can be used by the view system, which happens
-        in WebView since it (and its thread) know the current scale factor.
-     */
-    private void sendViewInvalidate(int left, int top, int right, int bottom) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                           WebViewClassic.INVAL_RECT_MSG_ID,
-                           new Rect(left, top, right, bottom)).sendToTarget();
-        }
-    }
-
-    private static boolean mRepaintScheduled = false;
-
-    /*
-     * Called by the WebView thread
-     */
-    /* package */ void signalRepaintDone() {
-        mRepaintScheduled = false;
-    }
-
-    // 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);
-
-    // called by JNI
-    private void didFirstLayout(boolean standardLoad) {
-        if (DebugFlags.WEB_VIEW_CORE) {
-            Log.v(LOGTAG, "didFirstLayout standardLoad =" + standardLoad);
-        }
-
-        mBrowserFrame.didFirstLayout();
-
-        if (mWebViewClassic == null) return;
-
-        boolean updateViewState = standardLoad || mIsRestored;
-        setupViewport(updateViewState);
-        // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
-        // be called after the WebView updates its state. If updateRestoreState
-        // is false, start to draw now as it is ready.
-        if (!updateViewState) {
-            mWebViewClassic.mViewManager.postReadyToDrawAll();
-        }
-
-        // remove the touch highlight when moving to a new page
-        mWebViewClassic.mPrivateHandler.sendEmptyMessage(
-                WebViewClassic.HIT_TEST_RESULT);
-
-        // reset the scroll position, the restored offset and scales
-        mRestoredX = mRestoredY = 0;
-        mIsRestored = false;
-        mRestoredScale = mRestoredTextWrapScale = 0;
-    }
-
-    // called by JNI
-    private void updateViewport() {
-        // Update viewport asap to make sure we get correct one.
-        setupViewport(true);
-    }
-
-    static float getFixedDisplayDensity(Context context) {
-        // We make bad assumptions about multiplying and dividing density by 100,
-        // force them to be true with this hack
-        float density = context.getResources().getDisplayMetrics().density;
-        return ((int) (density * 100)) / 100.0f;
-    }
-
-    private void setupViewport(boolean updateViewState) {
-        if (mWebViewClassic == null || mSettings == null) {
-            // We've been destroyed or are being destroyed, return early
-            return;
-        }
-        // set the viewport settings from WebKit
-        setViewportSettingsFromNative(mNativeClass);
-
-        // clamp initial scale
-        if (mViewportInitialScale > 0) {
-            if (mViewportMinimumScale > 0) {
-                mViewportInitialScale = Math.max(mViewportInitialScale,
-                        mViewportMinimumScale);
-            }
-            if (mViewportMaximumScale > 0) {
-                mViewportInitialScale = Math.min(mViewportInitialScale,
-                        mViewportMaximumScale);
-            }
-        }
-
-        if (mSettings.forceUserScalable()) {
-            mViewportUserScalable = true;
-            if (mViewportInitialScale > 0) {
-                if (mViewportMinimumScale > 0) {
-                    mViewportMinimumScale = Math.min(mViewportMinimumScale,
-                            mViewportInitialScale / 2);
-                }
-                if (mViewportMaximumScale > 0) {
-                    mViewportMaximumScale = Math.max(mViewportMaximumScale,
-                            mViewportInitialScale * 2);
-                }
-            } else {
-                if (mViewportMinimumScale > 0) {
-                    mViewportMinimumScale = Math.min(mViewportMinimumScale, 50);
-                }
-                if (mViewportMaximumScale > 0) {
-                    mViewportMaximumScale = Math.max(mViewportMaximumScale, 200);
-                }
-            }
-        }
-
-        // adjust the default scale to match the densityDpi
-        float adjust = 1.0f;
-        if (mViewportDensityDpi == -1) {
-            adjust = getFixedDisplayDensity(mContext);
-        } else if (mViewportDensityDpi > 0) {
-            adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
-                    / mViewportDensityDpi;
-            adjust = ((int) (adjust * 100)) / 100.0f;
-        }
-
-        // Remove any update density messages in flight.
-        // If the density is indeed different from WebView's default scale,
-        // a new message will be queued.
-        mWebViewClassic.mPrivateHandler.removeMessages(
-                WebViewClassic.UPDATE_ZOOM_DENSITY);
-        if (adjust != mWebViewClassic.getDefaultZoomScale()) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.UPDATE_ZOOM_DENSITY, adjust).sendToTarget();
-        }
-        int defaultScale = (int) (adjust * 100);
-
-        if (mViewportInitialScale > 0) {
-            mViewportInitialScale *= adjust;
-        }
-        if (mViewportMinimumScale > 0) {
-            mViewportMinimumScale *= adjust;
-        }
-        if (mViewportMaximumScale > 0) {
-            mViewportMaximumScale *= adjust;
-        }
-
-        // infer the values if they are not defined.
-        if (mViewportWidth == 0) {
-            if (mViewportInitialScale == 0) {
-                mViewportInitialScale = defaultScale;
-            }
-        }
-        if (mViewportUserScalable == false) {
-            mViewportInitialScale = defaultScale;
-            mViewportMinimumScale = defaultScale;
-            mViewportMaximumScale = defaultScale;
-        }
-        if (mViewportMinimumScale > mViewportInitialScale
-                && mViewportInitialScale != 0) {
-            mViewportMinimumScale = mViewportInitialScale;
-        }
-        if (mViewportMaximumScale > 0
-                && mViewportMaximumScale < mViewportInitialScale) {
-            mViewportMaximumScale = mViewportInitialScale;
-        }
-        if (mViewportWidth < 0 && mViewportInitialScale == defaultScale) {
-            mViewportWidth = 0;
-        }
-
-        // if mViewportWidth is 0, it means device-width, always update.
-        if (mViewportWidth != 0 && !updateViewState) {
-            // For non standard load, since updateViewState will be false.
-            mFirstLayoutForNonStandardLoad = true;
-            ViewState viewState = new ViewState();
-            viewState.mMinScale = mViewportMinimumScale / 100.0f;
-            viewState.mMaxScale = mViewportMaximumScale / 100.0f;
-            viewState.mDefaultScale = adjust;
-            // as mViewportWidth is not 0, it is not mobile site.
-            viewState.mMobileSite = false;
-            // for non-mobile site, we don't need minPrefWidth, set it as 0
-            viewState.mScrollX = 0;
-            viewState.mShouldStartScrolledRight = false;
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
-            return;
-        }
-
-        // now notify webview
-        // webViewWidth refers to the width in the view system
-        int webViewWidth;
-        // viewportWidth refers to the width in the document system
-        int viewportWidth = mCurrentViewWidth;
-        if (viewportWidth == 0) {
-            // 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 = mWebViewClassic.getViewWidth();
-            viewportWidth = (int) (webViewWidth / adjust);
-            if (viewportWidth == 0) {
-                if (DebugFlags.WEB_VIEW_CORE) {
-                    Log.v(LOGTAG, "Can't get the viewWidth yet");
-                }
-            }
-        } else {
-            webViewWidth = Math.round(viewportWidth * mCurrentViewScale);
-        }
-        mInitialViewState = new ViewState();
-        mInitialViewState.mMinScale = mViewportMinimumScale / 100.0f;
-        mInitialViewState.mMaxScale = mViewportMaximumScale / 100.0f;
-        mInitialViewState.mDefaultScale = adjust;
-        mInitialViewState.mScrollX = mRestoredX;
-        mInitialViewState.mScrollY = mRestoredY;
-        mInitialViewState.mShouldStartScrolledRight = (mRestoredX == 0)
-                && (mRestoredY == 0)
-                && (mBrowserFrame != null)
-                && mBrowserFrame.getShouldStartScrolledRight();
-
-        mInitialViewState.mMobileSite = (0 == mViewportWidth);
-        if (mIsRestored) {
-            mInitialViewState.mIsRestored = true;
-            mInitialViewState.mViewScale = mRestoredScale;
-            if (mRestoredTextWrapScale > 0) {
-                mInitialViewState.mTextWrapScale = mRestoredTextWrapScale;
-            } else {
-                mInitialViewState.mTextWrapScale = mInitialViewState.mViewScale;
-            }
-        } else {
-            if (mViewportInitialScale > 0) {
-                mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
-                        mViewportInitialScale / 100.0f;
-            } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth &&
-                !getSettings().getUseFixedViewport()) {
-                mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
-                        (float) webViewWidth / mViewportWidth;
-            } else {
-                mInitialViewState.mTextWrapScale = adjust;
-                if (mSettings.getUseWideViewPort()) {
-                    // 0 will trigger WebView to turn on zoom overview mode
-                    mInitialViewState.mViewScale = 0;
-                } else {
-                    mInitialViewState.mViewScale = adjust;
-                }
-            }
-        }
-
-        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.
-            mWebViewClassic.mLastHeightSent = 0;
-            // Send a negative scale to indicate that WebCore should reuse
-            // the current scale
-            WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
-            data.mWidth = mWebViewClassic.mLastWidthSent;
-            data.mHeight = 0;
-            // if mHeightCanMeasure is true, getUseWideViewPort() can't be
-            // true. It is safe to use mWidth for mTextWrapWidth.
-            data.mTextWrapWidth = data.mWidth;
-            data.mScale = -1.0f;
-            data.mIgnoreHeight = false;
-            data.mAnchorX = data.mAnchorY = 0;
-            // send VIEW_SIZE_CHANGED to the front of the queue so that we can
-            // avoid pushing the wrong picture to the WebView side. If there is
-            // a VIEW_SIZE_CHANGED in the queue, probably from WebView side,
-            // ignore it as we have a new size. If we leave VIEW_SIZE_CHANGED
-            // in the queue, as mLastHeightSent has been updated here, we may
-            // miss the requestLayout in WebView side after the new picture.
-            mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
-            mEventHub.sendMessageAtFrontOfQueue(Message.obtain(null,
-                    EventHub.VIEW_SIZE_CHANGED, data));
-        } else {
-            if (viewportWidth == 0) {
-                // Trick to ensure VIEW_SIZE_CHANGED will be sent from WebView
-                // to WebViewCore
-                mWebViewClassic.mLastWidthSent = 0;
-            } else {
-                WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
-                // mViewScale as 0 means it is in zoom overview mode. So we don't
-                // know the exact scale. If mRestoredScale is non-zero, use it;
-                // otherwise just use mTextWrapScale as the initial scale.
-                float tentativeScale = mInitialViewState.mViewScale;
-                if (tentativeScale == 0) {
-                    // The following tries to figure out more correct view scale
-                    // and text wrap scale to be sent to webkit, by using some
-                    // knowledge from web settings and zoom manager.
-
-                    // Calculated window width will be used to guess the scale
-                    // in zoom overview mode.
-                    tentativeScale = mInitialViewState.mTextWrapScale;
-                    int tentativeViewWidth = Math.round(webViewWidth / tentativeScale);
-                    int windowWidth = calculateWindowWidth(tentativeViewWidth);
-                    // In viewport setup time, since no content width is known, we assume
-                    // the windowWidth will be the content width, to get a more likely
-                    // zoom overview scale.
-                    data.mScale = (float) webViewWidth / windowWidth;
-                    if (!mSettings.getLoadWithOverviewMode()) {
-                        // If user choose non-overview mode.
-                        data.mScale = Math.max(data.mScale, tentativeScale);
-                    }
-                    if (mSettings.isNarrowColumnLayout()) {
-                        // In case of automatic text reflow in fixed view port mode.
-                        mInitialViewState.mTextWrapScale =
-                                mWebViewClassic.computeReadingLevelScale(data.mScale);
-                    }
-                } else {
-                    // Scale is given such as when page is restored, use it.
-                    data.mScale = tentativeScale;
-                }
-                if (DebugFlags.WEB_VIEW_CORE) {
-                    Log.v(LOGTAG, "setupViewport"
-                             + " mRestoredScale=" + mRestoredScale
-                             + " mViewScale=" + mInitialViewState.mViewScale
-                             + " mTextWrapScale=" + mInitialViewState.mTextWrapScale
-                             + " data.mScale= " + data.mScale
-                             );
-                }
-                data.mWidth = Math.round(webViewWidth / data.mScale);
-                // We may get a call here when mCurrentViewHeight == 0 if webcore completes the
-                // first layout before we sync our webview dimensions to it. In that case, we
-                // request the real height of the webview. This is not a perfect solution as we
-                // are calling a WebView method from the WebCore thread. But this is preferable
-                // to syncing an incorrect height.
-                data.mHeight = mCurrentViewHeight == 0 ?
-                        Math.round(mWebViewClassic.getViewHeight() / data.mScale)
-                        : Math.round((float) mCurrentViewHeight * data.mWidth / viewportWidth);
-                data.mTextWrapWidth = Math.round(webViewWidth
-                        / mInitialViewState.mTextWrapScale);
-                data.mIgnoreHeight = false;
-                data.mAnchorX = data.mAnchorY = 0;
-                // send VIEW_SIZE_CHANGED to the front of the queue so that we
-                // can avoid pushing the wrong picture to the WebView side.
-                mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
-                // Let webkit know the scale and inner width/height immediately
-                // in viewport setup time to avoid wrong information.
-                viewSizeChanged(data);
-            }
-        }
-    }
-
-    // called by JNI
-    private void restoreScale(float scale, float textWrapScale) {
-        if (mBrowserFrame.firstLayoutDone() == false) {
-            mIsRestored = true;
-            mRestoredScale = scale;
-            if (mSettings.getUseWideViewPort()) {
-                mRestoredTextWrapScale = textWrapScale;
-            }
-        }
-    }
-
-    // called by JNI
-    private void needTouchEvents(boolean need) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
-                    .sendToTarget();
-        }
-    }
-
-    // called by JNI
-    private void updateTextfield(int ptr, String text, int textGeneration) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr,
-                    textGeneration, text).sendToTarget();
-        }
-    }
-
-    private TextSelectionData createTextSelection(int start, int end, int selPtr) {
-        TextSelectionData data = new TextSelectionData(start, end, selPtr);
-        data.mSelectionReason = mTextSelectionChangeReason;
-        return data;
-    }
-
-    // called by JNI
-    private void updateTextSelection(int pointer, int start, int end,
-            int textGeneration, int selectionPtr) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration,
-                createTextSelection(start, end, selectionPtr)).sendToTarget();
-        }
-    }
-
-    // called by JNI
-    private void updateTextSizeAndScroll(int pointer, int width, int height,
-            int scrollX, int scrollY) {
-        if (mWebViewClassic != null) {
-            Rect rect = new Rect(-scrollX, -scrollY, width - scrollX,
-                    height - scrollY);
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.EDIT_TEXT_SIZE_CHANGED, pointer, 0, rect)
-                    .sendToTarget();
-        }
-    }
-
-    // called by JNI
-    private void clearTextEntry() {
-        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 (mWebViewClassic == null) {
-            return;
-        }
-        Message.obtain(mWebViewClassic.mPrivateHandler,
-                WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget();
-        Message.obtain(mWebViewClassic.mPrivateHandler,
-                WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID,
-                initData.mFieldPointer, 0,
-                createTextSelection(start, end, selectionPtr))
-                .sendToTarget();
-    }
-
-    private native void nativeRevealSelection(int nativeClass);
-    private native String nativeRequestLabel(int nativeClass, int framePtr,
-            int nodePtr);
-    /**
-     * Scroll the focused textfield to (xPercent, y) in document space
-     */
-    private native void nativeScrollFocusedTextInput(int nativeClass,
-            float xPercent, int y);
-
-    // these must be in document space (i.e. not scaled/zoomed).
-    private native void nativeSetScrollOffset(int nativeClass,
-            boolean sendScrollEvent, int dx, int dy);
-
-    private native void nativeSetGlobalBounds(int nativeClass, int x, int y,
-            int w, int h);
-
-    // called by JNI
-    private void requestListBox(String[] array, int[] enabledArray,
-            int[] selectedArray) {
-        if (mWebViewClassic != null) {
-            mWebViewClassic.requestListBox(array, enabledArray, selectedArray);
-        }
-    }
-
-    // called by JNI
-    private void requestListBox(String[] array, int[] enabledArray,
-            int selection) {
-        if (mWebViewClassic != null) {
-            mWebViewClassic.requestListBox(array, enabledArray, selection);
-        }
-
-    }
-
-    // called by JNI
-    private void requestKeyboard(boolean showKeyboard) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler,
-                    WebViewClassic.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0)
-                    .sendToTarget();
-        }
-    }
-
-    private void setWebTextViewAutoFillable(int queryId, String preview) {
-        if (mWebViewClassic != null) {
-            Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE,
-                    new AutoFillData(queryId, preview))
-                    .sendToTarget();
-        }
-    }
-
-    Context getContext() {
-        return mContext;
-    }
-
-    // called by JNI
-    private void keepScreenOn(boolean screenOn) {
-        if (mWebViewClassic != null) {
-            Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
-                    WebViewClassic.SCREEN_ON);
-            message.arg1 = screenOn ? 1 : 0;
-            message.sendToTarget();
-        }
-    }
-
-    // called by JNI
-    private Class<?> getPluginClass(String libName, String clsName) {
-
-        if (mWebViewClassic == null) {
-            return null;
-        }
-
-        PluginManager pluginManager = PluginManager.getInstance(null);
-
-        String pkgName = pluginManager.getPluginsAPKName(libName);
-        if (pkgName == null) {
-            Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
-            return null;
-        }
-
-        try {
-            return pluginManager.getPluginClass(pkgName, clsName);
-        } catch (NameNotFoundException e) {
-            Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")");
-        } catch (ClassNotFoundException e) {
-            Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
-                    ") in the apk (" + pkgName + ")");
-        }
-
-        return null;
-    }
-
-    // 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 (mWebViewClassic == null) {
-            return;
-        }
-
-        Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
-                WebViewClassic.SHOW_FULLSCREEN);
-        message.obj = childView.mView;
-        message.arg1 = orientation;
-        message.arg2 = npp;
-        message.sendToTarget();
-    }
-
-    // called by JNI
-    private void hideFullScreenPlugin() {
-        if (mWebViewClassic == null) {
-            return;
-        }
-        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
-                .sendToTarget();
-    }
-
-    private ViewManager.ChildView createSurface(View pluginView) {
-        if (mWebViewClassic == null) {
-            return null;
-        }
-
-        if (pluginView == null) {
-            Log.e(LOGTAG, "Attempted to add an empty plugin view to the view hierarchy");
-            return null;
-        }
-
-        // ensures the view system knows the view can redraw itself
-        pluginView.setWillNotDraw(false);
-
-        if(pluginView instanceof SurfaceView)
-            ((SurfaceView)pluginView).setZOrderOnTop(true);
-
-        ViewManager.ChildView view = mWebViewClassic.mViewManager.createView();
-        view.mView = pluginView;
-        return view;
-    }
-
-    // called by JNI.  PluginWidget functions for creating an embedded View for
-    // the surface drawing model.
-    private ViewManager.ChildView addSurface(View pluginView, int x, int y,
-                                             int width, int height) {
-        ViewManager.ChildView view = createSurface(pluginView);
-        view.attachView(x, y, width, height);
-        return view;
-    }
-
-    private void updateSurface(ViewManager.ChildView childView, int x, int y,
-            int width, int height) {
-        childView.attachView(x, y, width, height);
-    }
-
-    private void destroySurface(ViewManager.ChildView childView) {
-        childView.removeView();
-    }
-
-    // called by JNI
-    static class ShowRectData {
-        int mLeft;
-        int mTop;
-        int mWidth;
-        int mHeight;
-        int mContentWidth;
-        int mContentHeight;
-        float mXPercentInDoc;
-        float mXPercentInView;
-        float mYPercentInDoc;
-        float mYPercentInView;
-    }
-
-    private void showRect(int left, int top, int width, int height,
-            int contentWidth, int contentHeight, float xPercentInDoc,
-            float xPercentInView, float yPercentInDoc, float yPercentInView) {
-        if (mWebViewClassic != null) {
-            ShowRectData data = new ShowRectData();
-            data.mLeft = left;
-            data.mTop = top;
-            data.mWidth = width;
-            data.mHeight = height;
-            data.mContentWidth = contentWidth;
-            data.mContentHeight = contentHeight;
-            data.mXPercentInDoc = xPercentInDoc;
-            data.mXPercentInView = xPercentInView;
-            data.mYPercentInDoc = yPercentInDoc;
-            data.mYPercentInView = yPercentInView;
-            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 (mWebViewClassic == null) {
-            return;
-        }
-        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 (mWebViewClassic == null) {
-            return;
-        }
-        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES,
-                hMode, vMode).sendToTarget();
-    }
-
-    // called by JNI
-    private void selectAt(int x, int y) {
-        // TODO: Figure out what to do with this (b/6111818)
-    }
-
-    private void setUseMockDeviceOrientation() {
-        mDeviceMotionAndOrientationManager.setUseMock();
-    }
-
-    private void setUseMockGeolocation() {
-        mMockGeolocation.setUseMock();
-    }
-
-    public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
-        mMockGeolocation.setPosition(latitude, longitude, accuracy);
-    }
-
-    public void setMockGeolocationError(int code, String message) {
-        mMockGeolocation.setError(code, message);
-    }
-
-    public void setMockGeolocationPermission(boolean allow) {
-        mMockGeolocation.setPermission(allow);
-    }
-
-    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
-            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
-        mDeviceMotionAndOrientationManager.setMockOrientation(canProvideAlpha, alpha,
-                canProvideBeta, beta, canProvideGamma, gamma);
-    }
-
-    protected DeviceMotionService getDeviceMotionService() {
-        if (mDeviceMotionService == null) {
-            mDeviceMotionService =
-                    new DeviceMotionService(mDeviceMotionAndOrientationManager, mContext);
-        }
-        return mDeviceMotionService;
-    }
-
-    protected DeviceOrientationService getDeviceOrientationService() {
-        if (mDeviceOrientationService == null) {
-            mDeviceOrientationService =
-                    new DeviceOrientationService(mDeviceMotionAndOrientationManager, mContext);
-        }
-        return mDeviceOrientationService;
-    }
-
-    static void setShouldMonitorWebCoreThread() {
-        sShouldMonitorWebCoreThread = true;
-    }
-
-    private native void nativePause(int nativeClass);
-    private native void nativeResume(int nativeClass);
-    private native void nativeFreeMemory(int nativeClass);
-    private native void nativeFullScreenPluginHidden(int nativeClass, int npp);
-    private native void nativePluginSurfaceReady(int nativeClass);
-
-    private native WebKitHitTest nativeHitTest(int nativeClass, int x, int y,
-            int slop, boolean moveMouse);
-
-    private native void nativeAutoFillForm(int nativeClass, int queryId);
-    private native void nativeScrollLayer(int nativeClass, int layer, Rect rect);
-    private native int nativeFindAll(int nativeClass, String text);
-    private native int nativeFindNext(int nativeClass, boolean forward);
-
-    /**
-     * Deletes editable text between two points. Note that the selection may
-     * differ from the WebView's selection because the algorithms for selecting
-     * text differs for non-LTR text. Any text that isn't editable will be
-     * left unchanged.
-     * @param nativeClass The pointer to the native class (mNativeClass)
-     * @param startX The X position of the top-left selection point.
-     * @param startY The Y position of the top-left selection point.
-     * @param endX The X position of the bottom-right selection point.
-     * @param endY The Y position of the bottom-right selection point.
-     */
-    private native void nativeDeleteText(int nativeClass,
-            int startX, int startY, int endX, int endY);
-    /**
-     * Inserts text at the current cursor position. If the currently-focused
-     * node does not have a cursor position then this function does nothing.
-     */
-    private native void nativeInsertText(int nativeClass, String text);
-    /**
-     * Gets the text between two selection points. Note that the selection
-     * may differ from the WebView's selection because the algorithms for
-     * selecting text differs for non-LTR text.
-     * @param nativeClass The pointer to the native class (mNativeClass)
-     * @param startX The X position of the top-left selection point.
-     * @param startY The Y position of the top-left selection point.
-     * @param endX The X position of the bottom-right selection point.
-     * @param endY The Y position of the bottom-right selection point.
-     */
-    private native String nativeGetText(int nativeClass,
-            int startX, int startY, int endX, int endY);
-    private native void nativeSelectText(int nativeClass,
-            int handleId, int x, int y);
-    private native void nativeClearTextSelection(int nativeClass);
-    private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
-    private native void nativeSelectAll(int nativeClass);
-    private native void nativeSetInitialFocus(int nativeClass, int keyDirection);
-
-    private static native void nativeCertTrustChanged();
-}
diff --git a/core/java/android/webkit/WebViewDatabaseClassic.java b/core/java/android/webkit/WebViewDatabaseClassic.java
deleted file mode 100644
index be01028..0000000
--- a/core/java/android/webkit/WebViewDatabaseClassic.java
+++ /dev/null
@@ -1,628 +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;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.Map.Entry;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteStatement;
-import android.util.Log;
-
-final class WebViewDatabaseClassic extends WebViewDatabase {
-    private static final String LOGTAG = "WebViewDatabaseClassic";
-    private static final String DATABASE_FILE = "webview.db";
-    private static final String CACHE_DATABASE_FILE = "webviewCache.db";
-
-    private static final int DATABASE_VERSION = 11;
-    // 2 -> 3 Modified Cache table to allow cache of redirects
-    // 3 -> 4 Added Oma-Downloads table
-    // 4 -> 5 Modified Cache table to support persistent contentLength
-    // 5 -> 4 Removed Oma-Downoads table
-    // 5 -> 6 Add INDEX for cache table
-    // 6 -> 7 Change cache localPath from int to String
-    // 7 -> 8 Move cache to its own db
-    // 8 -> 9 Store both scheme and host when storing passwords
-    // 9 -> 10 Update httpauth table UNIQUE
-    // 10 -> 11 Drop cookies and cache now managed by the chromium stack,
-    //          and update the form data table to use the new format
-    //          implemented for b/5265606.
-
-    private static WebViewDatabaseClassic sInstance = null;
-    private static final Object sInstanceLock = new Object();
-
-    private static SQLiteDatabase sDatabase = null;
-
-    // synchronize locks
-    private final Object mPasswordLock = new Object();
-    private final Object mFormLock = new Object();
-    private final Object mHttpAuthLock = new Object();
-
-    private static final String mTableNames[] = {
-        "password", "formurl", "formdata", "httpauth"
-    };
-
-    // Table ids (they are index to mTableNames)
-    private static final int TABLE_PASSWORD_ID = 0;
-    private static final int TABLE_FORMURL_ID = 1;
-    private static final int TABLE_FORMDATA_ID = 2;
-    private static final int TABLE_HTTPAUTH_ID = 3;
-
-    // column id strings for "_id" which can be used by any table
-    private static final String ID_COL = "_id";
-
-    private static final String[] ID_PROJECTION = new String[] {
-        "_id"
-    };
-
-    // column id strings for "password" table
-    private static final String PASSWORD_HOST_COL = "host";
-    private static final String PASSWORD_USERNAME_COL = "username";
-    private static final String PASSWORD_PASSWORD_COL = "password";
-
-    // column id strings for "formurl" table
-    private static final String FORMURL_URL_COL = "url";
-
-    // column id strings for "formdata" table
-    private static final String FORMDATA_URLID_COL = "urlid";
-    private static final String FORMDATA_NAME_COL = "name";
-    private static final String FORMDATA_VALUE_COL = "value";
-
-    // column id strings for "httpauth" table
-    private static final String HTTPAUTH_HOST_COL = "host";
-    private static final String HTTPAUTH_REALM_COL = "realm";
-    private static final String HTTPAUTH_USERNAME_COL = "username";
-    private static final String HTTPAUTH_PASSWORD_COL = "password";
-
-    // Initially true until the background thread completes.
-    private boolean mInitialized = false;
-
-    private WebViewDatabaseClassic(final Context context) {
-        JniUtil.setContext(context);
-        new Thread() {
-            @Override
-            public void run() {
-                init(context);
-            }
-        }.start();
-
-        // Singleton only, use getInstance()
-    }
-
-    public static WebViewDatabaseClassic getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                sInstance = new WebViewDatabaseClassic(context);
-            }
-            return sInstance;
-        }
-    }
-
-    private synchronized void init(Context context) {
-        if (mInitialized) {
-            return;
-        }
-
-        initDatabase(context);
-        // Before using the Chromium HTTP stack, we stored the WebKit cache in
-        // our own DB. Clean up the DB file if it's still around.
-        context.deleteDatabase(CACHE_DATABASE_FILE);
-
-        // Thread done, notify.
-        mInitialized = true;
-        notify();
-    }
-
-    private void initDatabase(Context context) {
-        try {
-            sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
-        } catch (SQLiteException e) {
-            // try again by deleting the old db and create a new one
-            if (context.deleteDatabase(DATABASE_FILE)) {
-                sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
-                        null);
-            }
-        }
-
-        // sDatabase should not be null,
-        // the only case is RequestAPI test has problem to create db
-        if (sDatabase == null) {
-            mInitialized = true;
-            notify();
-            return;
-        }
-
-        if (sDatabase.getVersion() != DATABASE_VERSION) {
-            sDatabase.beginTransactionNonExclusive();
-            try {
-                upgradeDatabase();
-                sDatabase.setTransactionSuccessful();
-            } finally {
-                sDatabase.endTransaction();
-            }
-        }
-    }
-
-    private static void upgradeDatabase() {
-        upgradeDatabaseToV10();
-        upgradeDatabaseFromV10ToV11();
-        // Add future database upgrade functions here, one version at a
-        // time.
-        sDatabase.setVersion(DATABASE_VERSION);
-    }
-
-    private static void upgradeDatabaseFromV10ToV11() {
-        int oldVersion = sDatabase.getVersion();
-
-        if (oldVersion >= 11) {
-            // Nothing to do.
-            return;
-        }
-
-        // Clear out old java stack cookies - this data is now stored in
-        // a separate database managed by the Chrome stack.
-        sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
-
-        // Likewise for the old cache table.
-        sDatabase.execSQL("DROP TABLE IF EXISTS cache");
-
-        // Update form autocomplete  URLs to match new ICS formatting.
-        Cursor c = sDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
-                null, null, null, null);
-        while (c.moveToNext()) {
-            String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL)));
-            String url = c.getString(c.getColumnIndex(FORMURL_URL_COL));
-            ContentValues cv = new ContentValues(1);
-            cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url));
-            sDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?",
-                    new String[] { urlId });
-        }
-        c.close();
-    }
-
-    private static void upgradeDatabaseToV10() {
-        int oldVersion = sDatabase.getVersion();
-
-        if (oldVersion >= 10) {
-            // Nothing to do.
-            return;
-        }
-
-        if (oldVersion != 0) {
-            Log.i(LOGTAG, "Upgrading database from version "
-                    + oldVersion + " to "
-                    + DATABASE_VERSION + ", which will destroy old data");
-        }
-
-        if (9 == oldVersion) {
-            sDatabase.execSQL("DROP TABLE IF EXISTS "
-                    + mTableNames[TABLE_HTTPAUTH_ID]);
-            sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
-                    + " (" + ID_COL + " INTEGER PRIMARY KEY, "
-                    + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
-                    + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
-                    + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
-                    + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
-                    + ") ON CONFLICT REPLACE);");
-            return;
-        }
-
-        sDatabase.execSQL("DROP TABLE IF EXISTS cookies");
-        sDatabase.execSQL("DROP TABLE IF EXISTS cache");
-        sDatabase.execSQL("DROP TABLE IF EXISTS "
-                + mTableNames[TABLE_FORMURL_ID]);
-        sDatabase.execSQL("DROP TABLE IF EXISTS "
-                + mTableNames[TABLE_FORMDATA_ID]);
-        sDatabase.execSQL("DROP TABLE IF EXISTS "
-                + mTableNames[TABLE_HTTPAUTH_ID]);
-        sDatabase.execSQL("DROP TABLE IF EXISTS "
-                + mTableNames[TABLE_PASSWORD_ID]);
-
-        // formurl
-        sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
-                + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
-                + " TEXT" + ");");
-
-        // formdata
-        sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
-                + " (" + ID_COL + " INTEGER PRIMARY KEY, "
-                + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
-                + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
-                + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
-                + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
-
-        // httpauth
-        sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
-                + " (" + ID_COL + " INTEGER PRIMARY KEY, "
-                + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
-                + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
-                + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
-                + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
-                + ") ON CONFLICT REPLACE);");
-        // passwords
-        sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
-                + " (" + ID_COL + " INTEGER PRIMARY KEY, "
-                + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
-                + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
-                + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
-                + ") ON CONFLICT REPLACE);");
-    }
-
-    // Wait for the background initialization thread to complete and check the
-    // database creation status.
-    private boolean checkInitialized() {
-        synchronized (this) {
-            while (!mInitialized) {
-                try {
-                    wait();
-                } catch (InterruptedException e) {
-                    Log.e(LOGTAG, "Caught exception while checking " +
-                                  "initialization");
-                    Log.e(LOGTAG, Log.getStackTraceString(e));
-                }
-            }
-        }
-        return sDatabase != null;
-    }
-
-    private boolean hasEntries(int tableId) {
-        if (!checkInitialized()) {
-            return false;
-        }
-
-        Cursor cursor = null;
-        boolean ret = false;
-        try {
-            cursor = sDatabase.query(mTableNames[tableId], ID_PROJECTION,
-                    null, null, null, null, null);
-            ret = cursor.moveToFirst() == true;
-        } catch (IllegalStateException e) {
-            Log.e(LOGTAG, "hasEntries", e);
-        } finally {
-            if (cursor != null) cursor.close();
-        }
-        return ret;
-    }
-
-    //
-    // password functions
-    //
-
-    /**
-     * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
-     *
-     * @param schemePlusHost The scheme and host for the password
-     * @param username The username for the password. If it is null, it means
-     *            password can't be saved.
-     * @param password The password
-     */
-    void setUsernamePassword(String schemePlusHost, String username,
-                String password) {
-        if (schemePlusHost == null || !checkInitialized()) {
-            return;
-        }
-
-        synchronized (mPasswordLock) {
-            final ContentValues c = new ContentValues();
-            c.put(PASSWORD_HOST_COL, schemePlusHost);
-            c.put(PASSWORD_USERNAME_COL, username);
-            c.put(PASSWORD_PASSWORD_COL, password);
-            sDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
-                    c);
-        }
-    }
-
-    /**
-     * Retrieve the username and password for a given host
-     *
-     * @param schemePlusHost The scheme and host which passwords applies to
-     * @return String[] if found, String[0] is username, which can be null and
-     *         String[1] is password. Return null if it can't find anything.
-     */
-    String[] getUsernamePassword(String schemePlusHost) {
-        if (schemePlusHost == null || !checkInitialized()) {
-            return null;
-        }
-
-        final String[] columns = new String[] {
-                PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL
-        };
-        final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
-        synchronized (mPasswordLock) {
-            String[] ret = null;
-            Cursor cursor = null;
-            try {
-                cursor = sDatabase.query(mTableNames[TABLE_PASSWORD_ID],
-                        columns, selection, new String[] { schemePlusHost }, null,
-                        null, null);
-                if (cursor.moveToFirst()) {
-                    ret = new String[2];
-                    ret[0] = cursor.getString(
-                            cursor.getColumnIndex(PASSWORD_USERNAME_COL));
-                    ret[1] = cursor.getString(
-                            cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "getUsernamePassword", e);
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-            return ret;
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#hasUsernamePassword
-     */
-    @Override
-    public boolean hasUsernamePassword() {
-        synchronized (mPasswordLock) {
-            return hasEntries(TABLE_PASSWORD_ID);
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#clearUsernamePassword
-     */
-    @Override
-    public void clearUsernamePassword() {
-        if (!checkInitialized()) {
-            return;
-        }
-
-        synchronized (mPasswordLock) {
-            sDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null);
-        }
-    }
-
-    //
-    // http authentication password functions
-    //
-
-    /**
-     * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL,
-     * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique.
-     *
-     * @param host The host for the password
-     * @param realm The realm for the password
-     * @param username The username for the password. If it is null, it means
-     *            password can't be saved.
-     * @param password The password
-     */
-    void setHttpAuthUsernamePassword(String host, String realm, String username,
-            String password) {
-        if (host == null || realm == null || !checkInitialized()) {
-            return;
-        }
-
-        synchronized (mHttpAuthLock) {
-            final ContentValues c = new ContentValues();
-            c.put(HTTPAUTH_HOST_COL, host);
-            c.put(HTTPAUTH_REALM_COL, realm);
-            c.put(HTTPAUTH_USERNAME_COL, username);
-            c.put(HTTPAUTH_PASSWORD_COL, password);
-            sDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL,
-                    c);
-        }
-    }
-
-    /**
-     * Retrieve the HTTP authentication username and password for a given
-     * host+realm pair
-     *
-     * @param host The host the password applies to
-     * @param realm The realm the password applies to
-     * @return String[] if found, String[0] is username, which can be null and
-     *         String[1] is password. Return null if it can't find anything.
-     */
-    String[] getHttpAuthUsernamePassword(String host, String realm) {
-        if (host == null || realm == null || !checkInitialized()){
-            return null;
-        }
-
-        final String[] columns = new String[] {
-                HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
-        };
-        final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND ("
-                + HTTPAUTH_REALM_COL + " == ?)";
-        synchronized (mHttpAuthLock) {
-            String[] ret = null;
-            Cursor cursor = null;
-            try {
-                cursor = sDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
-                        columns, selection, new String[] { host, realm }, null,
-                        null, null);
-                if (cursor.moveToFirst()) {
-                    ret = new String[2];
-                    ret[0] = cursor.getString(
-                            cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
-                    ret[1] = cursor.getString(
-                            cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-            return ret;
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#hasHttpAuthUsernamePassword
-     */
-    @Override
-    public boolean hasHttpAuthUsernamePassword() {
-        synchronized (mHttpAuthLock) {
-            return hasEntries(TABLE_HTTPAUTH_ID);
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#clearHttpAuthUsernamePassword
-     */
-    @Override
-    public void clearHttpAuthUsernamePassword() {
-        if (!checkInitialized()) {
-            return;
-        }
-
-        synchronized (mHttpAuthLock) {
-            sDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null);
-        }
-    }
-
-    //
-    // form data functions
-    //
-
-    /**
-     * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL,
-     * FORMDATA_VALUE_COL) is unique
-     *
-     * @param url The url of the site
-     * @param formdata The form data in HashMap
-     */
-    void setFormData(String url, HashMap<String, String> formdata) {
-        if (url == null || formdata == null || !checkInitialized()) {
-            return;
-        }
-
-        final String selection = "(" + FORMURL_URL_COL + " == ?)";
-        synchronized (mFormLock) {
-            long urlid = -1;
-            Cursor cursor = null;
-            try {
-                cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
-                        ID_PROJECTION, selection, new String[] { url }, null, null,
-                        null);
-                if (cursor.moveToFirst()) {
-                    urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
-                } else {
-                    ContentValues c = new ContentValues();
-                    c.put(FORMURL_URL_COL, url);
-                    urlid = sDatabase.insert(
-                            mTableNames[TABLE_FORMURL_ID], null, c);
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "setFormData", e);
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-            if (urlid >= 0) {
-                Set<Entry<String, String>> set = formdata.entrySet();
-                Iterator<Entry<String, String>> iter = set.iterator();
-                ContentValues map = new ContentValues();
-                map.put(FORMDATA_URLID_COL, urlid);
-                while (iter.hasNext()) {
-                    Entry<String, String> entry = iter.next();
-                    map.put(FORMDATA_NAME_COL, entry.getKey());
-                    map.put(FORMDATA_VALUE_COL, entry.getValue());
-                    sDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map);
-                }
-            }
-        }
-    }
-
-    /**
-     * Get all the values for a form entry with "name" in a given site
-     *
-     * @param url The url of the site
-     * @param name The name of the form entry
-     * @return A list of values. Return empty list if nothing is found.
-     */
-    ArrayList<String> getFormData(String url, String name) {
-        ArrayList<String> values = new ArrayList<String>();
-        if (url == null || name == null || !checkInitialized()) {
-            return values;
-        }
-
-        final String urlSelection = "(" + FORMURL_URL_COL + " == ?)";
-        final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
-                + FORMDATA_NAME_COL + " == ?)";
-        synchronized (mFormLock) {
-            Cursor cursor = null;
-            try {
-                cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID],
-                        ID_PROJECTION, urlSelection, new String[] { url }, null,
-                        null, null);
-                while (cursor.moveToNext()) {
-                    long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
-                    Cursor dataCursor = null;
-                    try {
-                        dataCursor = sDatabase.query(
-                                mTableNames[TABLE_FORMDATA_ID],
-                                new String[] { ID_COL, FORMDATA_VALUE_COL },
-                                dataSelection,
-                                new String[] { Long.toString(urlid), name },
-                                null, null, null);
-                        if (dataCursor.moveToFirst()) {
-                            int valueCol = dataCursor.getColumnIndex(
-                                    FORMDATA_VALUE_COL);
-                            do {
-                                values.add(dataCursor.getString(valueCol));
-                            } while (dataCursor.moveToNext());
-                        }
-                    } catch (IllegalStateException e) {
-                        Log.e(LOGTAG, "getFormData dataCursor", e);
-                    } finally {
-                        if (dataCursor != null) dataCursor.close();
-                    }
-                }
-            } catch (IllegalStateException e) {
-                Log.e(LOGTAG, "getFormData cursor", e);
-            } finally {
-                if (cursor != null) cursor.close();
-            }
-            return values;
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#hasFormData
-     */
-    @Override
-    public boolean hasFormData() {
-        synchronized (mFormLock) {
-            return hasEntries(TABLE_FORMURL_ID);
-        }
-    }
-
-    /**
-     * @see WebViewDatabase#clearFormData
-     */
-    @Override
-    public void clearFormData() {
-        if (!checkInitialized()) {
-            return;
-        }
-
-        synchronized (mFormLock) {
-            sDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null);
-            sDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null);
-        }
-    }
-}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0fd4e33..b9131bf 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -28,27 +28,7 @@
  * @hide
  */
 public final class WebViewFactory {
-    private static final boolean DEFAULT_TO_EXPERIMENTAL_WEBVIEW = true;
-    // REMEMBER: property names must be <= 31 chars total.
-    private static final String EXPERIMENTAL_PROPERTY_DEFAULT_OFF = "persist.sys.webview.exp";
-    private static final String EXPERIMENTAL_PROPERTY_DEFAULT_ON =
-            "persist.sys.webview." + Build.ID;
 
-    // Modify the persisted property name when the new webview is on-by-default, so that any user
-    // setting override only lives as long as that build.
-    private static final String LONG_PROPERTY_NAME = DEFAULT_TO_EXPERIMENTAL_WEBVIEW ?
-            EXPERIMENTAL_PROPERTY_DEFAULT_ON : EXPERIMENTAL_PROPERTY_DEFAULT_OFF;
-    private static final String WEBVIEW_EXPERIMENTAL_PROPERTY =
-            LONG_PROPERTY_NAME.length() > SystemProperties.PROP_NAME_MAX ?
-            LONG_PROPERTY_NAME.substring(0, SystemProperties.PROP_NAME_MAX) : LONG_PROPERTY_NAME;
-
-    private static final String FORCE_PROVIDER_PROPERTY = "webview.force_provider";
-    private static final String FORCE_PROVIDER_PROPERTY_VALUE_CHROMIUM = "chromium";
-    private static final String FORCE_PROVIDER_PROPERTY_VALUE_CLASSIC = "classic";
-
-    // Default Provider factory class name.
-    // TODO: When the Chromium powered WebView is ready, it should be the default factory class.
-    private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory";
     private static final String CHROMIUM_WEBVIEW_FACTORY =
             "com.android.webview.chromium.WebViewChromiumFactoryProvider";
 
@@ -73,38 +53,31 @@
     private static final Object sProviderLock = new Object();
 
     public static boolean isExperimentalWebViewAvailable() {
-        try {
-            // Pass false so we don't initialize the class at this point, as this will be wasted if
-            // it's not enabled.
-            Class.forName(CHROMIUM_WEBVIEW_FACTORY, false, WebViewFactory.class.getClassLoader());
-            return true;
-        } catch (ClassNotFoundException e) {
-            return false;
-        }
+        // TODO: Remove callers of this method then remove it.
+        return false;  // Hide the toggle in Developer Settings.
     }
 
     /** @hide */
     public static void setUseExperimentalWebView(boolean enable) {
-        SystemProperties.set(WEBVIEW_EXPERIMENTAL_PROPERTY, enable ? "true" : "false");
-        Log.i(LOGTAG, "Use Experimental WebView changed: "
-                + SystemProperties.get(WebViewFactory.WEBVIEW_EXPERIMENTAL_PROPERTY, ""));
+        // TODO: Remove callers of this method then remove it.
     }
 
     /** @hide */
     public static boolean useExperimentalWebView() {
-        return SystemProperties.getBoolean(WEBVIEW_EXPERIMENTAL_PROPERTY,
-            DEFAULT_TO_EXPERIMENTAL_WEBVIEW);
+        // TODO: Remove callers of this method then remove it.
+        return true;
     }
 
     /** @hide */
     public static boolean isUseExperimentalWebViewSet() {
-        return !SystemProperties.get(WEBVIEW_EXPERIMENTAL_PROPERTY).isEmpty();
+        // TODO: Remove callers of this method then remove it.
+        return false;  // User has not modifed Developer Settings
     }
 
     static WebViewFactoryProvider getProvider() {
         synchronized (sProviderLock) {
             // For now the main purpose of this function (and the factory abstraction) is to keep
-            // us honest and minimize usage of WebViewClassic internals when binding the proxy.
+            // us honest and minimize usage of WebView internals when binding the proxy.
             if (sProviderInstance != null) return sProviderInstance;
 
             Class<WebViewFactoryProvider> providerClass;
@@ -138,26 +111,7 @@
         }
     }
 
-    // We allow a system property to specify that we should use the experimental Chromium powered
-    // WebView. This enables us to switch between implementations at runtime.
-    private static boolean isExperimentalWebViewEnabled() {
-        if (!isExperimentalWebViewAvailable()) return false;
-        String forceProviderName = SystemProperties.get(FORCE_PROVIDER_PROPERTY);
-        if (forceProviderName.isEmpty()) return useExperimentalWebView();
-
-        Log.i(LOGTAG, String.format("Provider overridden by property: %s=%s",
-                FORCE_PROVIDER_PROPERTY, forceProviderName));
-        if (forceProviderName.equals(FORCE_PROVIDER_PROPERTY_VALUE_CHROMIUM)) return true;
-        if (forceProviderName.equals(FORCE_PROVIDER_PROPERTY_VALUE_CLASSIC)) return false;
-        Log.e(LOGTAG, String.format("Unrecognized provider: %s", forceProviderName));
-        return useExperimentalWebView();
-    }
-
     private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
-        if (isExperimentalWebViewEnabled()) {
-            return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
-        } else  {
-            return (Class<WebViewFactoryProvider>) Class.forName(DEFAULT_WEBVIEW_FACTORY);
-        }
+        return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
     }
 }
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
deleted file mode 100644
index f64547f..0000000
--- a/core/java/android/webkit/WebViewInputDispatcher.java
+++ /dev/null
@@ -1,1293 +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;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-/**
- * Perform asynchronous dispatch of input events in a {@link WebView}.
- *
- * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
- * thread ({@link WebViewCore}).  The UI thread enqueues events for
- * processing, waits for the web kit thread to handle them, and then performs
- * additional processing depending on the outcome.
- *
- * How it works:
- *
- * 1. The web view thread receives an input event from the input system on the UI
- * thread in its {@link WebViewClassic#onTouchEvent} handler.  It sends the input event
- * to the dispatcher, then immediately returns true to the input system to indicate that
- * it will handle the event.
- *
- * 2. The web kit thread is notified that an event has been enqueued.  Meanwhile additional
- * events may be enqueued from the UI thread.  In some cases, the dispatcher may decide to
- * coalesce motion events into larger batches or to cancel events that have been
- * sitting in the queue for too long.
- *
- * 3. The web kit thread wakes up and handles all input events that are waiting for it.
- * After processing each input event, it informs the dispatcher whether the web application
- * has decided to handle the event itself and to prevent default event handling.
- *
- * 4. If web kit indicates that it wants to prevent default event handling, then web kit
- * consumes the remainder of the gesture and web view receives a cancel event if
- * needed.  Otherwise, the web view handles the gesture on the UI thread normally.
- *
- * 5. If the web kit thread takes too long to handle an input event, then it loses the
- * right to handle it.  The dispatcher synthesizes a cancellation event for web kit and
- * then tells the web view on the UI thread to handle the event that timed out along
- * with the rest of the gesture.
- *
- * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
- * is not necessarily what the web kit or UI thread will see.  As mentioned above, the
- * dispatcher may tweak the input event stream to improve responsiveness.  Both web view and
- * web kit are guaranteed to perceive a consistent stream of input events but
- * they might not always see the same events (especially if one decides
- * to prevent the other from handling a particular gesture).
- *
- * This implementation very deliberately does not refer to the {@link WebViewClassic}
- * or {@link WebViewCore} classes, preferring to communicate with them only via
- * interfaces to avoid unintentional coupling to their implementation details.
- *
- * Currently, the input dispatcher only handles pointer events (includes touch,
- * hover and scroll events).  In principle, it could be extended to handle trackball
- * and key events if needed.
- *
- * @hide
- */
-final class WebViewInputDispatcher {
-    private static final String TAG = "WebViewInputDispatcher";
-    private static final boolean DEBUG = false;
-    // This enables batching of MotionEvents. It will combine multiple MotionEvents
-    // together into a single MotionEvent if more events come in while we are
-    // still waiting on the processing of a previous event.
-    // If this is set to false, we will instead opt to drop ACTION_MOVE
-    // events we cannot keep up with.
-    // TODO: If batching proves to be working well, remove this
-    private static final boolean ENABLE_EVENT_BATCHING = true;
-
-    private final Object mLock = new Object();
-
-    // Pool of queued input events.  (guarded by mLock)
-    private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
-    private DispatchEvent mDispatchEventPool;
-    private int mDispatchEventPoolSize;
-
-    // Posted state, tracks events posted to the dispatcher.  (guarded by mLock)
-    private final TouchStream mPostTouchStream = new TouchStream();
-    private boolean mPostSendTouchEventsToWebKit;
-    private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
-    private boolean mPostLongPressScheduled;
-    private boolean mPostClickScheduled;
-    private boolean mPostShowTapHighlightScheduled;
-    private boolean mPostHideTapHighlightScheduled;
-    private int mPostLastWebKitXOffset;
-    private int mPostLastWebKitYOffset;
-    private float mPostLastWebKitScale;
-
-    // State for event tracking (click, longpress, double tap, etc..)
-    private boolean mIsDoubleTapCandidate;
-    private boolean mIsTapCandidate;
-    private float mInitialDownX;
-    private float mInitialDownY;
-    private float mTouchSlopSquared;
-    private float mDoubleTapSlopSquared;
-
-    // Web kit state, tracks events observed by web kit.  (guarded by mLock)
-    private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
-    private final TouchStream mWebKitTouchStream = new TouchStream();
-    private final WebKitCallbacks mWebKitCallbacks;
-    private final WebKitHandler mWebKitHandler;
-    private boolean mWebKitDispatchScheduled;
-    private boolean mWebKitTimeoutScheduled;
-    private long mWebKitTimeoutTime;
-
-    // UI state, tracks events observed by the UI.  (guarded by mLock)
-    private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
-    private final TouchStream mUiTouchStream = new TouchStream();
-    private final UiCallbacks mUiCallbacks;
-    private final UiHandler mUiHandler;
-    private boolean mUiDispatchScheduled;
-
-    // Give up on web kit handling of input events when this timeout expires.
-    private static final long WEBKIT_TIMEOUT_MILLIS = 200;
-    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
-    private static final int LONG_PRESS_TIMEOUT =
-            ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
-    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
-    private static final int PRESSED_STATE_DURATION = ViewConfiguration.getPressedStateDuration();
-
-    /**
-     * Event type: Indicates a touch event type.
-     *
-     * This event is delivered together with a {@link MotionEvent} with one of the
-     * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
-     * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
-     * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
-     */
-    public static final int EVENT_TYPE_TOUCH = 0;
-
-    /**
-     * Event type: Indicates a hover event type.
-     *
-     * This event is delivered together with a {@link MotionEvent} with one of the
-     * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
-     * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
-     */
-    public static final int EVENT_TYPE_HOVER = 1;
-
-    /**
-     * Event type: Indicates a scroll event type.
-     *
-     * This event is delivered together with a {@link MotionEvent} with action
-     * {@link MotionEvent#ACTION_SCROLL}.
-     */
-    public static final int EVENT_TYPE_SCROLL = 2;
-
-    /**
-     * Event type: Indicates a long-press event type.
-     *
-     * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
-     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
-     * that indicates the current touch coordinates of the long-press.
-     *
-     * This event is sent when the current touch gesture has been held longer than
-     * the long-press interval.
-     */
-    public static final int EVENT_TYPE_LONG_PRESS = 3;
-
-    /**
-     * Event type: Indicates a click event type.
-     *
-     * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
-     * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
-     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
-     * that indicates the location of the click.
-     *
-     * This event is sent shortly after the end of a touch after the double-tap
-     * interval has expired to indicate a click.
-     */
-    public static final int EVENT_TYPE_CLICK = 4;
-
-    /**
-     * Event type: Indicates a double-tap event type.
-     *
-     * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
-     * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
-     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
-     * that indicates the location of the double-tap.
-     *
-     * This event is sent immediately after a sequence of two touches separated
-     * in time by no more than the double-tap interval and separated in space
-     * by no more than the double-tap slop.
-     */
-    public static final int EVENT_TYPE_DOUBLE_TAP = 5;
-
-    /**
-     * Event type: Indicates that a hit test should be performed
-     */
-    public static final int EVENT_TYPE_HIT_TEST = 6;
-
-    /**
-     * Flag: This event is private to this queue.  Do not forward it.
-     */
-    public static final int FLAG_PRIVATE = 1 << 0;
-
-    /**
-     * Flag: This event is currently being processed by web kit.
-     * If a timeout occurs, make a copy of it before forwarding the event to another queue.
-     */
-    public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
-
-    /**
-     * Flag: A timeout occurred while waiting for web kit to process this input event.
-     */
-    public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
-
-    /**
-     * Flag: Indicates that the event was transformed for delivery to web kit.
-     * The event must be transformed back before being delivered to the UI.
-     */
-    public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
-
-    public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
-        this.mUiCallbacks = uiCallbacks;
-        mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
-
-        this.mWebKitCallbacks = webKitCallbacks;
-        mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
-
-        ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
-        mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
-        mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
-        mTouchSlopSquared = config.getScaledTouchSlop();
-        mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
-    }
-
-    /**
-     * Sets whether web kit wants to receive touch events.
-     *
-     * @param enable True to enable dispatching of touch events to web kit, otherwise
-     * web kit will be skipped.
-     */
-    public void setWebKitWantsTouchEvents(boolean enable) {
-        if (DEBUG) {
-            Log.d(TAG, "webkitWantsTouchEvents: " + enable);
-        }
-        synchronized (mLock) {
-            if (mPostSendTouchEventsToWebKit != enable) {
-                if (!enable) {
-                    enqueueWebKitCancelTouchEventIfNeededLocked();
-                }
-                mPostSendTouchEventsToWebKit = enable;
-            }
-        }
-    }
-
-    /**
-     * Posts a pointer event to the dispatch queue.
-     *
-     * @param event The event to post.
-     * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
-     * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
-     * @param webKitScale The scale factor to apply to translated events before dispatching
-     * them to web kit.
-     * @return True if the dispatcher will handle the event, false if the event is unsupported.
-     */
-    public boolean postPointerEvent(MotionEvent event,
-            int webKitXOffset, int webKitYOffset, float webKitScale) {
-        if (event == null) {
-            throw new IllegalArgumentException("event cannot be null");
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "postPointerEvent: " + event);
-        }
-
-        final int action = event.getActionMasked();
-        final int eventType;
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_POINTER_DOWN:
-            case MotionEvent.ACTION_POINTER_UP:
-            case MotionEvent.ACTION_CANCEL:
-                eventType = EVENT_TYPE_TOUCH;
-                break;
-            case MotionEvent.ACTION_SCROLL:
-                eventType = EVENT_TYPE_SCROLL;
-                break;
-            case MotionEvent.ACTION_HOVER_ENTER:
-            case MotionEvent.ACTION_HOVER_MOVE:
-            case MotionEvent.ACTION_HOVER_EXIT:
-                eventType = EVENT_TYPE_HOVER;
-                break;
-            default:
-                return false; // currently unsupported event type
-        }
-
-        synchronized (mLock) {
-            // Ensure that the event is consistent and should be delivered.
-            MotionEvent eventToEnqueue = event;
-            if (eventType == EVENT_TYPE_TOUCH) {
-                eventToEnqueue = mPostTouchStream.update(event);
-                if (eventToEnqueue == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "postPointerEvent: dropped event " + event);
-                    }
-                    unscheduleLongPressLocked();
-                    unscheduleClickLocked();
-                    hideTapCandidateLocked();
-                    return false;
-                }
-
-                if (action == MotionEvent.ACTION_DOWN && mPostSendTouchEventsToWebKit) {
-                    if (mUiCallbacks.shouldInterceptTouchEvent(eventToEnqueue)) {
-                        mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
-                    } else if (mPostDoNotSendTouchEventsToWebKitUntilNextGesture) {
-                        // Recover from a previous web kit timeout.
-                        mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
-                    }
-                }
-            }
-
-            // Copy the event because we need to retain ownership.
-            if (eventToEnqueue == event) {
-                eventToEnqueue = event.copy();
-            }
-
-            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
-                    webKitXOffset, webKitYOffset, webKitScale);
-            updateStateTrackersLocked(d, event);
-            enqueueEventLocked(d);
-        }
-        return true;
-    }
-
-    private void scheduleLongPressLocked() {
-        unscheduleLongPressLocked();
-        mPostLongPressScheduled = true;
-        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
-                LONG_PRESS_TIMEOUT);
-    }
-
-    private void unscheduleLongPressLocked() {
-        if (mPostLongPressScheduled) {
-            mPostLongPressScheduled = false;
-            mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
-        }
-    }
-
-    private void postLongPress() {
-        synchronized (mLock) {
-            if (!mPostLongPressScheduled) {
-                return;
-            }
-            mPostLongPressScheduled = false;
-
-            MotionEvent event = mPostTouchStream.getLastEvent();
-            if (event == null) {
-                return;
-            }
-
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_MOVE:
-                case MotionEvent.ACTION_POINTER_DOWN:
-                case MotionEvent.ACTION_POINTER_UP:
-                    break;
-                default:
-                    return;
-            }
-
-            MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
-            eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
-            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
-                    mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
-            enqueueEventLocked(d);
-        }
-    }
-
-    private void hideTapCandidateLocked() {
-        unscheduleHideTapHighlightLocked();
-        unscheduleShowTapHighlightLocked();
-        mUiCallbacks.showTapHighlight(false);
-    }
-
-    private void showTapCandidateLocked() {
-        unscheduleHideTapHighlightLocked();
-        unscheduleShowTapHighlightLocked();
-        mUiCallbacks.showTapHighlight(true);
-    }
-
-    private void scheduleShowTapHighlightLocked() {
-        unscheduleShowTapHighlightLocked();
-        mPostShowTapHighlightScheduled = true;
-        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_SHOW_TAP_HIGHLIGHT,
-                TAP_TIMEOUT);
-    }
-
-    private void unscheduleShowTapHighlightLocked() {
-        if (mPostShowTapHighlightScheduled) {
-            mPostShowTapHighlightScheduled = false;
-            mUiHandler.removeMessages(UiHandler.MSG_SHOW_TAP_HIGHLIGHT);
-        }
-    }
-
-    private void scheduleHideTapHighlightLocked() {
-        unscheduleHideTapHighlightLocked();
-        mPostHideTapHighlightScheduled = true;
-        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_HIDE_TAP_HIGHLIGHT,
-                PRESSED_STATE_DURATION);
-    }
-
-    private void unscheduleHideTapHighlightLocked() {
-        if (mPostHideTapHighlightScheduled) {
-            mPostHideTapHighlightScheduled = false;
-            mUiHandler.removeMessages(UiHandler.MSG_HIDE_TAP_HIGHLIGHT);
-        }
-    }
-
-    private void postShowTapHighlight(boolean show) {
-        synchronized (mLock) {
-            if (show) {
-                if (!mPostShowTapHighlightScheduled) {
-                    return;
-                }
-                mPostShowTapHighlightScheduled = false;
-            } else {
-                if (!mPostHideTapHighlightScheduled) {
-                    return;
-                }
-                mPostHideTapHighlightScheduled = false;
-            }
-            mUiCallbacks.showTapHighlight(show);
-        }
-    }
-
-    private void scheduleClickLocked() {
-        unscheduleClickLocked();
-        mPostClickScheduled = true;
-        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
-    }
-
-    private void unscheduleClickLocked() {
-        if (mPostClickScheduled) {
-            mPostClickScheduled = false;
-            mUiHandler.removeMessages(UiHandler.MSG_CLICK);
-        }
-    }
-
-    private void postClick() {
-        synchronized (mLock) {
-            if (!mPostClickScheduled) {
-                return;
-            }
-            mPostClickScheduled = false;
-
-            MotionEvent event = mPostTouchStream.getLastEvent();
-            if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
-                return;
-            }
-
-            showTapCandidateLocked();
-            MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
-            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
-                    mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
-            enqueueEventLocked(d);
-        }
-    }
-
-    private void checkForDoubleTapOnDownLocked(MotionEvent event) {
-        mIsDoubleTapCandidate = false;
-        if (!mPostClickScheduled) {
-            return;
-        }
-        int deltaX = (int) mInitialDownX - (int) event.getX();
-        int deltaY = (int) mInitialDownY - (int) event.getY();
-        if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
-            unscheduleClickLocked();
-            mIsDoubleTapCandidate = true;
-        }
-    }
-
-    private boolean isClickCandidateLocked(MotionEvent event) {
-        if (event == null
-                || event.getActionMasked() != MotionEvent.ACTION_UP
-                || !mIsTapCandidate) {
-            return false;
-        }
-        long downDuration = event.getEventTime() - event.getDownTime();
-        return downDuration < LONG_PRESS_TIMEOUT;
-    }
-
-    private void enqueueDoubleTapLocked(MotionEvent event) {
-        MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
-        DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
-                mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
-        enqueueEventLocked(d);
-    }
-
-    private void enqueueHitTestLocked(MotionEvent event) {
-        mUiCallbacks.clearPreviousHitTest();
-        MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
-        DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_HIT_TEST, 0,
-                mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
-        enqueueEventLocked(d);
-    }
-
-    private void checkForSlopLocked(MotionEvent event) {
-        if (!mIsTapCandidate) {
-            return;
-        }
-        int deltaX = (int) mInitialDownX - (int) event.getX();
-        int deltaY = (int) mInitialDownY - (int) event.getY();
-        if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
-            unscheduleLongPressLocked();
-            mIsTapCandidate = false;
-            hideTapCandidateLocked();
-        }
-    }
-
-    private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
-        mPostLastWebKitXOffset = d.mWebKitXOffset;
-        mPostLastWebKitYOffset = d.mWebKitYOffset;
-        mPostLastWebKitScale = d.mWebKitScale;
-        int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
-        if (d.mEventType != EVENT_TYPE_TOUCH) {
-            return;
-        }
-
-        if (action == MotionEvent.ACTION_CANCEL
-                || event.getPointerCount() > 1) {
-            unscheduleLongPressLocked();
-            unscheduleClickLocked();
-            hideTapCandidateLocked();
-            mIsDoubleTapCandidate = false;
-            mIsTapCandidate = false;
-            hideTapCandidateLocked();
-        } else if (action == MotionEvent.ACTION_DOWN) {
-            checkForDoubleTapOnDownLocked(event);
-            scheduleLongPressLocked();
-            mIsTapCandidate = true;
-            mInitialDownX = event.getX();
-            mInitialDownY = event.getY();
-            enqueueHitTestLocked(event);
-            if (mIsDoubleTapCandidate) {
-                hideTapCandidateLocked();
-            } else {
-                scheduleShowTapHighlightLocked();
-            }
-        } else if (action == MotionEvent.ACTION_UP) {
-            unscheduleLongPressLocked();
-            if (isClickCandidateLocked(event)) {
-                if (mIsDoubleTapCandidate) {
-                    hideTapCandidateLocked();
-                    enqueueDoubleTapLocked(event);
-                } else {
-                    scheduleClickLocked();
-                }
-            } else {
-                hideTapCandidateLocked();
-            }
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            checkForSlopLocked(event);
-        }
-    }
-
-    /**
-     * Dispatches pending web kit events.
-     * Must only be called from the web kit thread.
-     *
-     * This method may be used to flush the queue of pending input events
-     * immediately.  This method may help to reduce input dispatch latency
-     * if called before certain expensive operations such as drawing.
-     */
-    public void dispatchWebKitEvents() {
-        dispatchWebKitEvents(false);
-    }
-
-    private void dispatchWebKitEvents(boolean calledFromHandler) {
-        for (;;) {
-            // Get the next event, but leave it in the queue so we can move it to the UI
-            // queue if a timeout occurs.
-            DispatchEvent d;
-            MotionEvent event;
-            final int eventType;
-            int flags;
-            synchronized (mLock) {
-                if (!ENABLE_EVENT_BATCHING) {
-                    drainStaleWebKitEventsLocked();
-                }
-                d = mWebKitDispatchEventQueue.mHead;
-                if (d == null) {
-                    if (mWebKitDispatchScheduled) {
-                        mWebKitDispatchScheduled = false;
-                        if (!calledFromHandler) {
-                            mWebKitHandler.removeMessages(
-                                    WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
-                        }
-                    }
-                    return;
-                }
-
-                event = d.mEvent;
-                if (event != null) {
-                    event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
-                    event.scale(d.mWebKitScale);
-                    d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
-                }
-
-                eventType = d.mEventType;
-                if (eventType == EVENT_TYPE_TOUCH) {
-                    event = mWebKitTouchStream.update(event);
-                    if (DEBUG && event == null && d.mEvent != null) {
-                        Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
-                    }
-                }
-
-                d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
-                flags = d.mFlags;
-            }
-
-            // Handle the event.
-            final boolean preventDefault;
-            if (event == null) {
-                preventDefault = false;
-            } else {
-                preventDefault = dispatchWebKitEvent(event, eventType, flags);
-            }
-
-            synchronized (mLock) {
-                flags = d.mFlags;
-                d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
-                boolean recycleEvent = event != d.mEvent;
-
-                if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
-                    // A timeout occurred!
-                    recycleDispatchEventLocked(d);
-                } else {
-                    // Web kit finished in a timely manner.  Dequeue the event.
-                    assert mWebKitDispatchEventQueue.mHead == d;
-                    mWebKitDispatchEventQueue.dequeue();
-
-                    updateWebKitTimeoutLocked();
-
-                    if ((flags & FLAG_PRIVATE) != 0) {
-                        // Event was intended for web kit only.  All done.
-                        recycleDispatchEventLocked(d);
-                    } else if (preventDefault) {
-                        // Web kit has decided to consume the event!
-                        if (d.mEventType == EVENT_TYPE_TOUCH) {
-                            enqueueUiCancelTouchEventIfNeededLocked();
-                            unscheduleLongPressLocked();
-                        }
-                    } else {
-                        // Web kit is being friendly.  Pass the event to the UI.
-                        enqueueUiEventUnbatchedLocked(d);
-                    }
-                }
-
-                if (event != null && recycleEvent) {
-                    event.recycle();
-                }
-
-                if (eventType == EVENT_TYPE_CLICK) {
-                    scheduleHideTapHighlightLocked();
-                }
-            }
-        }
-    }
-
-    // Runs on web kit thread.
-    private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
-        if (DEBUG) {
-            Log.d(TAG, "dispatchWebKitEvent: event=" + event
-                    + ", eventType=" + eventType + ", flags=" + flags);
-        }
-        boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
-                this, event, eventType, flags);
-        if (DEBUG) {
-            Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
-        }
-        return preventDefault;
-    }
-
-    private boolean isMoveEventLocked(DispatchEvent d) {
-        return d.mEvent != null
-                && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
-    }
-
-    private void drainStaleWebKitEventsLocked() {
-        DispatchEvent d = mWebKitDispatchEventQueue.mHead;
-        while (d != null && d.mNext != null
-                && isMoveEventLocked(d)
-                && isMoveEventLocked(d.mNext)) {
-            DispatchEvent next = d.mNext;
-            skipWebKitEventLocked(d);
-            d = next;
-        }
-        mWebKitDispatchEventQueue.mHead = d;
-    }
-
-    // Called by WebKit when it doesn't care about the rest of the touch stream
-    public void skipWebkitForRemainingTouchStream() {
-        // Just treat this like a timeout
-        handleWebKitTimeout();
-    }
-
-    // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
-    private void handleWebKitTimeout() {
-        synchronized (mLock) {
-            if (!mWebKitTimeoutScheduled) {
-                return;
-            }
-            mWebKitTimeoutScheduled = false;
-
-            if (DEBUG) {
-                Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
-            }
-
-            // Drain the web kit event queue.
-            DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
-
-            // If web kit was processing an event (must be at the head of the list because
-            // it can only do one at a time), then clone it or ignore it.
-            if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
-                d.mFlags |= FLAG_WEBKIT_TIMEOUT;
-                if ((d.mFlags & FLAG_PRIVATE) != 0) {
-                    d = d.mNext; // the event is private to web kit, ignore it
-                } else {
-                    d = copyDispatchEventLocked(d);
-                    d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
-                }
-            }
-
-            // Enqueue all non-private events for handling by the UI thread.
-            while (d != null) {
-                DispatchEvent next = d.mNext;
-                skipWebKitEventLocked(d);
-                d = next;
-            }
-
-            // Tell web kit to cancel all pending touches.
-            // This also prevents us from sending web kit any more touches until the
-            // next gesture begins.  (As required to ensure touch event stream consistency.)
-            enqueueWebKitCancelTouchEventIfNeededLocked();
-        }
-    }
-
-    private void skipWebKitEventLocked(DispatchEvent d) {
-        d.mNext = null;
-        if ((d.mFlags & FLAG_PRIVATE) != 0) {
-            recycleDispatchEventLocked(d);
-        } else {
-            d.mFlags |= FLAG_WEBKIT_TIMEOUT;
-            enqueueUiEventUnbatchedLocked(d);
-        }
-    }
-
-    /**
-     * Dispatches pending UI events.
-     * Must only be called from the UI thread.
-     *
-     * This method may be used to flush the queue of pending input events
-     * immediately.  This method may help to reduce input dispatch latency
-     * if called before certain expensive operations such as drawing.
-     */
-    public void dispatchUiEvents() {
-        dispatchUiEvents(false);
-    }
-
-    private void dispatchUiEvents(boolean calledFromHandler) {
-        for (;;) {
-            MotionEvent event;
-            final int eventType;
-            final int flags;
-            synchronized (mLock) {
-                DispatchEvent d = mUiDispatchEventQueue.dequeue();
-                if (d == null) {
-                    if (mUiDispatchScheduled) {
-                        mUiDispatchScheduled = false;
-                        if (!calledFromHandler) {
-                            mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
-                        }
-                    }
-                    return;
-                }
-
-                event = d.mEvent;
-                if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
-                    event.scale(1.0f / d.mWebKitScale);
-                    event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
-                    d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
-                }
-
-                eventType = d.mEventType;
-                if (eventType == EVENT_TYPE_TOUCH) {
-                    event = mUiTouchStream.update(event);
-                    if (DEBUG && event == null && d.mEvent != null) {
-                        Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
-                    }
-                }
-
-                flags = d.mFlags;
-
-                if (event == d.mEvent) {
-                    d.mEvent = null; // retain ownership of event, don't recycle it yet
-                }
-                recycleDispatchEventLocked(d);
-
-                if (eventType == EVENT_TYPE_CLICK) {
-                    scheduleHideTapHighlightLocked();
-                }
-            }
-
-            // Handle the event.
-            if (event != null) {
-                dispatchUiEvent(event, eventType, flags);
-                event.recycle();
-            }
-        }
-    }
-
-    // Runs on UI thread.
-    private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
-        if (DEBUG) {
-            Log.d(TAG, "dispatchUiEvent: event=" + event
-                    + ", eventType=" + eventType + ", flags=" + flags);
-        }
-        mUiCallbacks.dispatchUiEvent(event, eventType, flags);
-    }
-
-    private void enqueueEventLocked(DispatchEvent d) {
-        if (!shouldSkipWebKit(d)) {
-            enqueueWebKitEventLocked(d);
-        } else {
-            enqueueUiEventLocked(d);
-        }
-    }
-
-    private boolean shouldSkipWebKit(DispatchEvent d) {
-        switch (d.mEventType) {
-            case EVENT_TYPE_CLICK:
-            case EVENT_TYPE_HOVER:
-            case EVENT_TYPE_SCROLL:
-            case EVENT_TYPE_HIT_TEST:
-                return false;
-            case EVENT_TYPE_TOUCH:
-                // TODO: This should be cleaned up. We now have WebViewInputDispatcher
-                // and WebViewClassic both checking for slop and doing their own
-                // thing - they should be consolidated. And by consolidated, I mean
-                // WebViewClassic's version should just be deleted.
-                // The reason this is done is because webpages seem to expect
-                // that they only get an ontouchmove if the slop has been exceeded.
-                if (mIsTapCandidate && d.mEvent != null
-                        && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {
-                    return true;
-                }
-                return !mPostSendTouchEventsToWebKit
-                        || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
-        }
-        return true;
-    }
-
-    private void enqueueWebKitCancelTouchEventIfNeededLocked() {
-        // We want to cancel touch events that were delivered to web kit.
-        // Enqueue a null event at the end of the queue if needed.
-        if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
-            DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
-                    0, 0, 1.0f);
-            enqueueWebKitEventUnbatchedLocked(d);
-            mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
-        }
-    }
-
-    private void enqueueWebKitEventLocked(DispatchEvent d) {
-        if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
-            if (DEBUG) {
-                Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
-            }
-            recycleDispatchEventLocked(d);
-        } else {
-            enqueueWebKitEventUnbatchedLocked(d);
-        }
-    }
-
-    private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
-        if (DEBUG) {
-            Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
-        }
-        mWebKitDispatchEventQueue.enqueue(d);
-        scheduleWebKitDispatchLocked();
-        updateWebKitTimeoutLocked();
-    }
-
-    private void scheduleWebKitDispatchLocked() {
-        if (!mWebKitDispatchScheduled) {
-            mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
-            mWebKitDispatchScheduled = true;
-        }
-    }
-
-    private void updateWebKitTimeoutLocked() {
-        DispatchEvent d = mWebKitDispatchEventQueue.mHead;
-        if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
-            return;
-        }
-        if (mWebKitTimeoutScheduled) {
-            mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
-            mWebKitTimeoutScheduled = false;
-        }
-        if (d != null) {
-            mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
-            mWebKitTimeoutScheduled = true;
-            mWebKitTimeoutTime = d.mTimeoutTime;
-        }
-    }
-
-    private void enqueueUiCancelTouchEventIfNeededLocked() {
-        // We want to cancel touch events that were delivered to the UI.
-        // Enqueue a null event at the end of the queue if needed.
-        if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
-            DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
-                    0, 0, 1.0f);
-            enqueueUiEventUnbatchedLocked(d);
-        }
-    }
-
-    private void enqueueUiEventLocked(DispatchEvent d) {
-        if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
-            if (DEBUG) {
-                Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
-            }
-            recycleDispatchEventLocked(d);
-        } else {
-            enqueueUiEventUnbatchedLocked(d);
-        }
-    }
-
-    private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
-        if (DEBUG) {
-            Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
-        }
-        mUiDispatchEventQueue.enqueue(d);
-        scheduleUiDispatchLocked();
-    }
-
-    private void scheduleUiDispatchLocked() {
-        if (!mUiDispatchScheduled) {
-            mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
-            mUiDispatchScheduled = true;
-        }
-    }
-
-    private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
-        if (!ENABLE_EVENT_BATCHING) {
-            return false;
-        }
-        if (tail != null && tail.mEvent != null && in.mEvent != null
-                && in.mEventType == tail.mEventType
-                && in.mFlags == tail.mFlags
-                && in.mWebKitXOffset == tail.mWebKitXOffset
-                && in.mWebKitYOffset == tail.mWebKitYOffset
-                && in.mWebKitScale == tail.mWebKitScale) {
-            return tail.mEvent.addBatch(in.mEvent);
-        }
-        return false;
-    }
-
-    private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
-            int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
-        DispatchEvent d = obtainUninitializedDispatchEventLocked();
-        d.mEvent = event;
-        d.mEventType = eventType;
-        d.mFlags = flags;
-        d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
-        d.mWebKitXOffset = webKitXOffset;
-        d.mWebKitYOffset = webKitYOffset;
-        d.mWebKitScale = webKitScale;
-        if (DEBUG) {
-            Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
-        }
-        return d;
-    }
-
-    private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
-        DispatchEvent copy = obtainUninitializedDispatchEventLocked();
-        if (d.mEvent != null) {
-            copy.mEvent = d.mEvent.copy();
-        }
-        copy.mEventType = d.mEventType;
-        copy.mFlags = d.mFlags;
-        copy.mTimeoutTime = d.mTimeoutTime;
-        copy.mWebKitXOffset = d.mWebKitXOffset;
-        copy.mWebKitYOffset = d.mWebKitYOffset;
-        copy.mWebKitScale = d.mWebKitScale;
-        copy.mNext = d.mNext;
-        return copy;
-    }
-
-    private DispatchEvent obtainUninitializedDispatchEventLocked() {
-        DispatchEvent d = mDispatchEventPool;
-        if (d != null) {
-            mDispatchEventPoolSize -= 1;
-            mDispatchEventPool = d.mNext;
-            d.mNext = null;
-        } else {
-            d = new DispatchEvent();
-        }
-        return d;
-    }
-
-    private void recycleDispatchEventLocked(DispatchEvent d) {
-        if (d.mEvent != null) {
-            d.mEvent.recycle();
-            d.mEvent = null;
-        }
-
-        if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
-            mDispatchEventPoolSize += 1;
-            d.mNext = mDispatchEventPool;
-            mDispatchEventPool = d;
-        }
-    }
-
-    /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
-    public static interface UiCallbacks {
-        /**
-         * Gets the UI thread's looper.
-         * @return The looper.
-         */
-        public Looper getUiLooper();
-
-        /**
-         * Gets the UI's context
-         * @return The context
-         */
-        public Context getContext();
-
-        /**
-         * Dispatches an event to the UI.
-         * @param event The event.
-         * @param eventType The event type.
-         * @param flags The event's dispatch flags.
-         */
-        public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
-
-        /**
-         * Asks the UI thread whether this touch event stream should be
-         * intercepted based on the touch down event.
-         * @param event The touch down event.
-         * @return true if the UI stream wants the touch stream without going
-         * through webkit or false otherwise.
-         */
-        public boolean shouldInterceptTouchEvent(MotionEvent event);
-
-        /**
-         * Inform's the UI that it should show the tap highlight
-         * @param show True if it should show the highlight, false if it should hide it
-         */
-        public void showTapHighlight(boolean show);
-
-        /**
-         * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so
-         * previous hit tests should be cleared as they are obsolete.
-         */
-        public void clearPreviousHitTest();
-    }
-
-    /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
-    public static interface WebKitCallbacks {
-        /**
-         * Gets the web kit thread's looper.
-         * @return The looper.
-         */
-        public Looper getWebKitLooper();
-
-        /**
-         * Dispatches an event to web kit.
-         * @param dispatcher The WebViewInputDispatcher sending the event
-         * @param event The event.
-         * @param eventType The event type.
-         * @param flags The event's dispatch flags.
-         * @return True if web kit wants to prevent default event handling.
-         */
-        public boolean dispatchWebKitEvent(WebViewInputDispatcher dispatcher,
-                MotionEvent event, int eventType, int flags);
-    }
-
-    // Runs on UI thread.
-    private final class UiHandler extends Handler {
-        public static final int MSG_DISPATCH_UI_EVENTS = 1;
-        public static final int MSG_WEBKIT_TIMEOUT = 2;
-        public static final int MSG_LONG_PRESS = 3;
-        public static final int MSG_CLICK = 4;
-        public static final int MSG_SHOW_TAP_HIGHLIGHT = 5;
-        public static final int MSG_HIDE_TAP_HIGHLIGHT = 6;
-
-        public UiHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DISPATCH_UI_EVENTS:
-                    dispatchUiEvents(true);
-                    break;
-                case MSG_WEBKIT_TIMEOUT:
-                    handleWebKitTimeout();
-                    break;
-                case MSG_LONG_PRESS:
-                    postLongPress();
-                    break;
-                case MSG_CLICK:
-                    postClick();
-                    break;
-                case MSG_SHOW_TAP_HIGHLIGHT:
-                    postShowTapHighlight(true);
-                    break;
-                case MSG_HIDE_TAP_HIGHLIGHT:
-                    postShowTapHighlight(false);
-                    break;
-                default:
-                    throw new IllegalStateException("Unknown message type: " + msg.what);
-            }
-        }
-    }
-
-    // Runs on web kit thread.
-    private final class WebKitHandler extends Handler {
-        public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
-
-        public WebKitHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DISPATCH_WEBKIT_EVENTS:
-                    dispatchWebKitEvents(true);
-                    break;
-                default:
-                    throw new IllegalStateException("Unknown message type: " + msg.what);
-            }
-        }
-    }
-
-    private static final class DispatchEvent {
-        public DispatchEvent mNext;
-
-        public MotionEvent mEvent;
-        public int mEventType;
-        public int mFlags;
-        public long mTimeoutTime;
-        public int mWebKitXOffset;
-        public int mWebKitYOffset;
-        public float mWebKitScale;
-    }
-
-    private static final class DispatchEventQueue {
-        public DispatchEvent mHead;
-        public DispatchEvent mTail;
-
-        public boolean isEmpty() {
-            return mHead != null;
-        }
-
-        public void enqueue(DispatchEvent d) {
-            if (mHead == null) {
-                mHead = d;
-                mTail = d;
-            } else {
-                mTail.mNext = d;
-                mTail = d;
-            }
-        }
-
-        public DispatchEvent dequeue() {
-            DispatchEvent d = mHead;
-            if (d != null) {
-                DispatchEvent next = d.mNext;
-                if (next == null) {
-                    mHead = null;
-                    mTail = null;
-                } else {
-                    mHead = next;
-                    d.mNext = null;
-                }
-            }
-            return d;
-        }
-
-        public DispatchEvent dequeueList() {
-            DispatchEvent d = mHead;
-            if (d != null) {
-                mHead = null;
-                mTail = null;
-            }
-            return d;
-        }
-    }
-
-    /**
-     * Keeps track of a stream of touch events so that we can discard touch
-     * events that would make the stream inconsistent.
-     */
-    private static final class TouchStream {
-        private MotionEvent mLastEvent;
-
-        /**
-         * Gets the last touch event that was delivered.
-         * @return The last touch event, or null if none.
-         */
-        public MotionEvent getLastEvent() {
-            return mLastEvent;
-        }
-
-        /**
-         * Updates the touch event stream.
-         * @param event The event that we intend to send, or null to cancel the
-         * touch event stream.
-         * @return The event that we should actually send, or null if no event should
-         * be sent because the proposed event would make the stream inconsistent.
-         */
-        public MotionEvent update(MotionEvent event) {
-            if (event == null) {
-                if (isCancelNeeded()) {
-                    event = mLastEvent;
-                    if (event != null) {
-                        event.setAction(MotionEvent.ACTION_CANCEL);
-                        mLastEvent = null;
-                    }
-                }
-                return event;
-            }
-
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_MOVE:
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_POINTER_DOWN:
-                case MotionEvent.ACTION_POINTER_UP:
-                    if (mLastEvent == null
-                            || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
-                        return null;
-                    }
-                    updateLastEvent(event);
-                    return event;
-
-                case MotionEvent.ACTION_DOWN:
-                    updateLastEvent(event);
-                    return event;
-
-                case MotionEvent.ACTION_CANCEL:
-                    if (mLastEvent == null) {
-                        return null;
-                    }
-                    updateLastEvent(null);
-                    return event;
-
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Returns true if there is a gesture in progress that may need to be canceled.
-         * @return True if cancel is needed.
-         */
-        public boolean isCancelNeeded() {
-            return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
-        }
-
-        private void updateLastEvent(MotionEvent event) {
-            if (mLastEvent != null) {
-                mLastEvent.recycle();
-            }
-            mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/webkit/ZoomControlBase.java b/core/java/android/webkit/ZoomControlBase.java
deleted file mode 100644
index be9e8f3..0000000
--- a/core/java/android/webkit/ZoomControlBase.java
+++ /dev/null
@@ -1,41 +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.
- */
-package android.webkit;
-
-interface ZoomControlBase {
-
-    /**
-     * Causes the on-screen zoom control to be made visible
-     */
-    public void show();
-
-    /**
-     * Causes the on-screen zoom control to disappear
-     */
-    public void hide();
-
-    /**
-     * Enables the control to update its state if necessary in response to a
-     * change in the pages zoom level. For example, if the max zoom level is
-     * reached then the control can disable the button for zooming in.
-     */
-    public void update();
-
-    /**
-     * Checks to see if the control is currently visible to the user.
-     */
-    public boolean isVisible();
-}
diff --git a/core/java/android/webkit/ZoomControlEmbedded.java b/core/java/android/webkit/ZoomControlEmbedded.java
deleted file mode 100644
index ae19832..0000000
--- a/core/java/android/webkit/ZoomControlEmbedded.java
+++ /dev/null
@@ -1,119 +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.
- */
-package android.webkit;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-import android.widget.ZoomButtonsController;
-
-class ZoomControlEmbedded implements ZoomControlBase {
-
-    private final ZoomManager mZoomManager;
-    private final WebViewClassic mWebView;
-
-    // The controller is lazily initialized in getControls() for performance.
-    private ZoomButtonsController mZoomButtonsController;
-
-    public ZoomControlEmbedded(ZoomManager zoomManager, WebViewClassic webView) {
-        mZoomManager = zoomManager;
-        mWebView = webView;
-    }
-
-    public void show() {
-        if (!getControls().isVisible() && !mZoomManager.isZoomScaleFixed()) {
-
-            mZoomButtonsController.setVisible(true);
-
-            if (mZoomManager.isDoubleTapEnabled()) {
-                WebSettingsClassic settings = mWebView.getSettings();
-                int count = settings.getDoubleTapToastCount();
-                if (mZoomManager.isInZoomOverview() && count > 0) {
-                    settings.setDoubleTapToastCount(--count);
-                    Toast.makeText(mWebView.getContext(),
-                            com.android.internal.R.string.double_tap_toast,
-                            Toast.LENGTH_LONG).show();
-                }
-            }
-        }
-    }
-
-    public void hide() {
-        if (mZoomButtonsController != null) {
-            mZoomButtonsController.setVisible(false);
-        }
-    }
-
-    public boolean isVisible() {
-        return mZoomButtonsController != null && mZoomButtonsController.isVisible();
-    }
-
-    public void update() {
-        if (mZoomButtonsController == null) {
-            return;
-        }
-
-        boolean canZoomIn = mZoomManager.canZoomIn();
-        boolean canZoomOut = mZoomManager.canZoomOut() && !mZoomManager.isInZoomOverview();
-        if (!canZoomIn && !canZoomOut) {
-            // Hide the zoom in and out buttons if the page cannot zoom
-            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
-        } else {
-            // Set each one individually, as a page may be able to zoom in or out
-            mZoomButtonsController.setZoomInEnabled(canZoomIn);
-            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
-        }
-    }
-
-    private ZoomButtonsController getControls() {
-        if (mZoomButtonsController == null) {
-            mZoomButtonsController = new ZoomButtonsController(mWebView.getWebView());
-            mZoomButtonsController.setOnZoomListener(new ZoomListener());
-            // ZoomButtonsController positions the buttons at the bottom, but in
-            // the middle. Change their layout parameters so they appear on the
-            // right.
-            View controls = mZoomButtonsController.getZoomControls();
-            ViewGroup.LayoutParams params = controls.getLayoutParams();
-            if (params instanceof FrameLayout.LayoutParams) {
-                ((FrameLayout.LayoutParams) params).gravity = Gravity.END;
-            }
-        }
-        return mZoomButtonsController;
-    }
-
-    private class ZoomListener implements ZoomButtonsController.OnZoomListener {
-
-        public void onVisibilityChanged(boolean visible) {
-            if (visible) {
-                mWebView.switchOutDrawHistory();
-                // Bring back the hidden zoom controls.
-                mZoomButtonsController.getZoomControls().setVisibility(View.VISIBLE);
-                update();
-            }
-        }
-
-        public void onZoom(boolean zoomIn) {
-            if (zoomIn) {
-                mWebView.zoomIn();
-            } else {
-                mWebView.zoomOut();
-            }
-            update();
-        }
-    }
-}
diff --git a/core/java/android/webkit/ZoomControlExternal.java b/core/java/android/webkit/ZoomControlExternal.java
deleted file mode 100644
index f5bfc05..0000000
--- a/core/java/android/webkit/ZoomControlExternal.java
+++ /dev/null
@@ -1,159 +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.
- */
-package android.webkit;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.View.OnClickListener;
-import android.view.animation.AlphaAnimation;
-import android.widget.FrameLayout;
-
-@Deprecated
-class ZoomControlExternal implements ZoomControlBase {
-
-    // The time that the external controls are visible before fading away
-    private static final long ZOOM_CONTROLS_TIMEOUT =
-            ViewConfiguration.getZoomControlsTimeout();
-    // The view containing the external zoom controls
-    private ExtendedZoomControls mZoomControls;
-    private Runnable mZoomControlRunnable;
-    private final Handler mPrivateHandler = new Handler();
-
-    private final WebViewClassic mWebView;
-
-    public ZoomControlExternal(WebViewClassic webView) {
-        mWebView = webView;
-    }
-
-    public void show() {
-        if(mZoomControlRunnable != null) {
-            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-        }
-        getControls().show(true);
-        mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
-    }
-
-    public void hide() {
-        if (mZoomControlRunnable != null) {
-            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-        }
-        if (mZoomControls != null) {
-            mZoomControls.hide();
-        }
-    }
-
-    public boolean isVisible() {
-        return mZoomControls != null && mZoomControls.isShown();
-    }
-
-    public void update() { }
-
-    public ExtendedZoomControls getControls() {
-        if (mZoomControls == null) {
-            mZoomControls = createZoomControls();
-
-            /*
-             * need to be set to VISIBLE first so that getMeasuredHeight() in
-             * {@link #onSizeChanged()} can return the measured value for proper
-             * layout.
-             */
-            mZoomControls.setVisibility(View.VISIBLE);
-            mZoomControlRunnable = new Runnable() {
-                public void run() {
-                    /* Don't dismiss the controls if the user has
-                     * focus on them. Wait and check again later.
-                     */
-                    if (!mZoomControls.hasFocus()) {
-                        mZoomControls.hide();
-                    } else {
-                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                        mPrivateHandler.postDelayed(mZoomControlRunnable,
-                                ZOOM_CONTROLS_TIMEOUT);
-                    }
-                }
-            };
-        }
-        return mZoomControls;
-    }
-
-    private ExtendedZoomControls createZoomControls() {
-        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mWebView.getContext());
-        zoomControls.setOnZoomInClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                // reset time out
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
-                mWebView.zoomIn();
-            }
-        });
-        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                // reset time out
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
-                mWebView.zoomOut();
-            }
-        });
-        return zoomControls;
-    }
-
-    private static class ExtendedZoomControls extends FrameLayout {
-
-        private android.widget.ZoomControls mPlusMinusZoomControls;
-
-        public ExtendedZoomControls(Context context) {
-            super(context, null);
-            LayoutInflater inflater = (LayoutInflater)
-                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
-            mPlusMinusZoomControls = (android.widget.ZoomControls) findViewById(
-                    com.android.internal.R.id.zoomControls);
-            findViewById(com.android.internal.R.id.zoomMagnify).setVisibility(
-                    View.GONE);
-        }
-
-        public void show(boolean showZoom) {
-            mPlusMinusZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
-            fade(View.VISIBLE, 0.0f, 1.0f);
-        }
-
-        public void hide() {
-            fade(View.GONE, 1.0f, 0.0f);
-        }
-
-        private void fade(int visibility, float startAlpha, float endAlpha) {
-            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
-            anim.setDuration(500);
-            startAnimation(anim);
-            setVisibility(visibility);
-        }
-
-        public boolean hasFocus() {
-            return mPlusMinusZoomControls.hasFocus();
-        }
-
-        public void setOnZoomInClickListener(OnClickListener listener) {
-            mPlusMinusZoomControls.setOnZoomInClickListener(listener);
-        }
-
-        public void setOnZoomOutClickListener(OnClickListener listener) {
-            mPlusMinusZoomControls.setOnZoomOutClickListener(listener);
-        }
-    }
-}
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
deleted file mode 100644
index 1d864e5..0000000
--- a/core/java/android/webkit/ZoomManager.java
+++ /dev/null
@@ -1,1263 +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.
- */
-
-package android.webkit;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Canvas;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.FloatMath;
-import android.util.Log;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-
-/**
- * The ZoomManager is responsible for maintaining the WebView's current zoom
- * level state.  It is also responsible for managing the on-screen zoom controls
- * as well as any animation of the WebView due to zooming.
- *
- * Currently, there are two methods for animating the zoom of a WebView.
- *
- * (1) The first method is triggered by startZoomAnimation(...) and is a fixed
- * length animation where the final zoom scale is known at startup.  This type of
- * animation notifies webkit of the final scale BEFORE it animates. The animation
- * is then done by scaling the CANVAS incrementally based on a stepping function.
- *
- * (2) The second method is triggered by a multi-touch pinch and the new scale
- * is determined dynamically based on the user's gesture. This type of animation
- * only notifies webkit of new scale AFTER the gesture is complete. The animation
- * effect is achieved by scaling the VIEWS (both WebView and ViewManager.ChildView)
- * to the new scale in response to events related to the user's gesture.
- */
-class ZoomManager {
-
-    static final String LOGTAG = "webviewZoom";
-
-    private final WebViewClassic mWebView;
-    private final CallbackProxy mCallbackProxy;
-
-    // Widgets responsible for the on-screen zoom functions of the WebView.
-    private ZoomControlEmbedded mEmbeddedZoomControl;
-    private ZoomControlExternal mExternalZoomControl;
-
-    /*
-     * The scale factors that determine the upper and lower bounds for the
-     * default zoom scale.
-     */
-    protected static final float DEFAULT_MAX_ZOOM_SCALE_FACTOR = 4.00f;
-    protected static final float DEFAULT_MIN_ZOOM_SCALE_FACTOR = 0.25f;
-
-    // The default scale limits, which are dependent on the display density.
-    private float mDefaultMaxZoomScale;
-    private float mDefaultMinZoomScale;
-
-    // The actual scale limits, which can be set through a webpage's viewport
-    // meta-tag.
-    private float mMaxZoomScale;
-    private float mMinZoomScale;
-
-    // Locks the minimum ZoomScale to the value currently set in mMinZoomScale.
-    private boolean mMinZoomScaleFixed = true;
-
-    /*
-     * When loading a new page the WebView does not initially know the final
-     * width of the page. Therefore, when a new page is loaded in overview mode
-     * the overview scale is initialized to a default value. This flag is then
-     * set and used to notify the ZoomManager to take the width of the next
-     * picture from webkit and use that width to enter into zoom overview mode.
-     */
-    private boolean mInitialZoomOverview = false;
-
-    /*
-     * When in the zoom overview mode, the page's width is fully fit to the
-     * current window. Additionally while the page is in this state it is
-     * active, in other words, you can click to follow the links. We cache a
-     * boolean to enable us to quickly check whether or not we are in overview
-     * mode, but this value should only be modified by changes to the zoom
-     * scale.
-     */
-    private boolean mInZoomOverview = false;
-    private int mZoomOverviewWidth;
-    private float mInvZoomOverviewWidth;
-
-    /*
-     * These variables track the center point of the zoom and they are used to
-     * determine the point around which we should zoom. They are stored in view
-     * coordinates.
-     */
-    private float mZoomCenterX;
-    private float mZoomCenterY;
-
-    /*
-     * Similar to mZoomCenterX(Y), these track the focus point of the scale
-     * gesture. The difference is these get updated every time when onScale is
-     * invoked no matter if a zooming really happens.
-     */
-    private float mFocusX;
-    private float mFocusY;
-
-    /*
-     * mFocusMovementQueue keeps track of the previous focus point movement
-     * has been through. Comparing to the difference of the gesture's previous
-     * span and current span, it determines if the gesture is for panning or
-     * zooming or both.
-     */
-    private FocusMovementQueue mFocusMovementQueue;
-
-    /*
-     * These values represent the point around which the screen should be
-     * centered after zooming. In other words it is used to determine the center
-     * point of the visible document after the page has finished zooming. This
-     * is important because the zoom may have potentially reflowed the text and
-     * we need to ensure the proper portion of the document remains on the
-     * screen.
-     */
-    private int mAnchorX;
-    private int mAnchorY;
-
-    // The scale factor that is used to determine the column width for text
-    private float mTextWrapScale;
-
-    /*
-     * The default zoom scale is the scale factor used when the user triggers a
-     * zoom in by double tapping on the WebView. The value is initially set
-     * based on the display density, but can be changed at any time via the
-     * WebSettings.
-     */
-    private float mDefaultScale;
-    private float mInvDefaultScale;
-
-    /*
-     * The logical density of the display. This is a scaling factor for the
-     * Density Independent Pixel unit, where one DIP is one pixel on an
-     * approximately 160 dpi screen (see android.util.DisplayMetrics.density)
-     */
-    private float mDisplayDensity;
-
-    /*
-     * The factor that is used to tweak the zoom scale on a double-tap,
-     * and can be changed via WebSettings. Range is from 0.75f to 1.25f.
-     */
-    private float mDoubleTapZoomFactor = 1.0f;
-
-    /*
-     * The scale factor that is used as the minimum increment when going from
-     * overview to reading level on a double tap.
-     */
-    private static float MIN_DOUBLE_TAP_SCALE_INCREMENT = 0.5f;
-
-    // the current computed zoom scale and its inverse.
-    private float mActualScale;
-    private float mInvActualScale;
-    
-    /*
-     * The initial scale for the WebView. 0 means default. If initial scale is
-     * greater than 0, the WebView starts with this value as its initial scale.
-     */
-    private float mInitialScale;
-
-    private static float MINIMUM_SCALE_INCREMENT = 0.007f;
-
-    /*
-     *  The touch points could be changed even the fingers stop moving.
-     *  We use the following to filter out the zooming jitters.
-     */
-    private static float MINIMUM_SCALE_WITHOUT_JITTER = 0.007f;
-
-    /*
-     * The following member variables are only to be used for animating zoom. If
-     * mZoomScale is non-zero then we are in the middle of a zoom animation. The
-     * other variables are used as a cache (e.g. inverse) or as a way to store
-     * the state of the view prior to animating (e.g. initial scroll coords).
-     */
-    private float mZoomScale;
-    private float mInvInitialZoomScale;
-    private float mInvFinalZoomScale;
-    private int mInitialScrollX;
-    private int mInitialScrollY;
-    private long mZoomStart;
-
-    private static final int ZOOM_ANIMATION_LENGTH = 175;
-
-    // whether support multi-touch
-    private boolean mSupportMultiTouch;
-    
-    /**
-     * True if we have a touch panel capable of detecting smooth pan/scale at the same time
-     */
-    private boolean mAllowPanAndScale;
-
-    // use the framework's ScaleGestureDetector to handle scaling gestures
-    private ScaleGestureDetector mScaleDetector;
-    private boolean mPinchToZoomAnimating = false;
-
-    private boolean mHardwareAccelerated = false;
-    private boolean mInHWAcceleratedZoom = false;
-
-    public ZoomManager(WebViewClassic webView, CallbackProxy callbackProxy) {
-        mWebView = webView;
-        mCallbackProxy = callbackProxy;
-
-        /*
-         * Ideally mZoomOverviewWidth should be mContentWidth. But sites like
-         * ESPN and Engadget always have wider mContentWidth no matter what the
-         * viewport size is.
-         */
-        setZoomOverviewWidth(WebViewClassic.DEFAULT_VIEWPORT_WIDTH);
-
-        mFocusMovementQueue = new FocusMovementQueue();
-    }
-
-    /**
-     * Initialize both the default and actual zoom scale to the given density.
-     *
-     * @param density The logical density of the display. This is a scaling factor
-     * for the Density Independent Pixel unit, where one DIP is one pixel on an
-     * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
-     */
-    public void init(float density) {
-        assert density > 0;
-
-        mDisplayDensity = density;
-        setDefaultZoomScale(density);
-        mActualScale = density;
-        mInvActualScale = 1 / density;
-        mTextWrapScale = getReadingLevelScale();
-    }
-
-    /**
-     * Update the default zoom scale using the given density. It will also reset
-     * the current min and max zoom scales to the default boundaries as well as
-     * ensure that the actual scale falls within those boundaries.
-     *
-     * @param density The logical density of the display. This is a scaling factor
-     * for the Density Independent Pixel unit, where one DIP is one pixel on an
-     * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
-     */
-    public void updateDefaultZoomDensity(float density) {
-        assert density > 0;
-
-        if (Math.abs(density - mDefaultScale) > MINIMUM_SCALE_INCREMENT) {
-            // Remember the current zoom density before it gets changed.
-            final float originalDefault = mDefaultScale;
-            // set the new default density
-            mDisplayDensity = density;
-            setDefaultZoomScale(density);
-            float scaleChange = (originalDefault > 0.0) ? density / originalDefault: 1.0f;
-            // adjust the scale if it falls outside the new zoom bounds
-            setZoomScale(mActualScale * scaleChange, true);
-        }
-    }
-
-    private void setDefaultZoomScale(float defaultScale) {
-        final float originalDefault = mDefaultScale;
-        mDefaultScale = defaultScale;
-        mInvDefaultScale = 1 / defaultScale;
-        mDefaultMaxZoomScale = defaultScale * DEFAULT_MAX_ZOOM_SCALE_FACTOR;
-        mDefaultMinZoomScale = defaultScale * DEFAULT_MIN_ZOOM_SCALE_FACTOR;
-        if (originalDefault > 0.0 && mMaxZoomScale > 0.0) {
-            // Keeps max zoom scale when zoom density changes.
-            mMaxZoomScale = defaultScale / originalDefault * mMaxZoomScale;
-        } else {
-            mMaxZoomScale = mDefaultMaxZoomScale;
-        }
-        if (originalDefault > 0.0 && mMinZoomScale > 0.0) {
-            // Keeps min zoom scale when zoom density changes.
-            mMinZoomScale = defaultScale / originalDefault * mMinZoomScale;
-        } else {
-            mMinZoomScale = mDefaultMinZoomScale;
-        }
-        if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) {
-            mMaxZoomScale = mMinZoomScale;
-        }
-        sanitizeMinMaxScales();
-    }
-
-    public final float getScale() {
-        return mActualScale;
-    }
-
-    public final float getInvScale() {
-        return mInvActualScale;
-    }
-
-    public final float getTextWrapScale() {
-        return mTextWrapScale;
-    }
-
-    public final float getMaxZoomScale() {
-        return mMaxZoomScale;
-    }
-
-    public final float getMinZoomScale() {
-        return mMinZoomScale;
-    }
-
-    public final float getDefaultScale() {
-        return mDefaultScale;
-    }
-
-    /**
-     * Returns the zoom scale used for reading text on a double-tap.
-     */
-    public final float getReadingLevelScale() {
-        return computeScaleWithLimits(computeReadingLevelScale(getZoomOverviewScale()));
-    }
-
-    /* package */ final float computeReadingLevelScale(float scale) {
-        return Math.max(mDisplayDensity * mDoubleTapZoomFactor,
-                scale + MIN_DOUBLE_TAP_SCALE_INCREMENT);
-    }
-
-    public final float getInvDefaultScale() {
-        return mInvDefaultScale;
-    }
-
-    public final float getDefaultMaxZoomScale() {
-        return mDefaultMaxZoomScale;
-    }
-
-    public final float getDefaultMinZoomScale() {
-        return mDefaultMinZoomScale;
-    }
-
-    public final int getDocumentAnchorX() {
-        return mAnchorX;
-    }
-
-    public final int getDocumentAnchorY() {
-        return mAnchorY;
-    }
-
-    public final void clearDocumentAnchor() {
-        mAnchorX = mAnchorY = 0;
-    }
-
-    public final void setZoomCenter(float x, float y) {
-        mZoomCenterX = x;
-        mZoomCenterY = y;
-    }
-
-    public final void setInitialScaleInPercent(int scaleInPercent) {
-        mInitialScale = scaleInPercent * 0.01f;
-    }
-
-    public final float computeScaleWithLimits(float scale) {
-        if (scale < mMinZoomScale) {
-            scale = mMinZoomScale;
-        } else if (scale > mMaxZoomScale) {
-            scale = mMaxZoomScale;
-        }
-        return scale;
-    }
-
-    public final boolean isScaleOverLimits(float scale) {
-        return scale <= mMinZoomScale || scale >= mMaxZoomScale;
-    }
-
-    public final boolean isZoomScaleFixed() {
-        return mMinZoomScale >= mMaxZoomScale;
-    }
-
-    public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
-        return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
-    }
-
-    public boolean willScaleTriggerZoom(float scale) {
-        return exceedsMinScaleIncrement(scale, mActualScale);
-    }
-
-    public final boolean canZoomIn() {
-        return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
-    }
-
-    public final boolean canZoomOut() {
-        return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
-    }
-
-    public boolean zoomIn() {
-        return zoom(1.25f);
-    }
-
-    public boolean zoomOut() {
-        return zoom(0.8f);
-    }
-
-    // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
-    private boolean zoom(float zoomMultiplier) {
-        mInitialZoomOverview = false;
-        // TODO: alternatively we can disallow this during draw history mode
-        mWebView.switchOutDrawHistory();
-        // Center zooming to the center of the screen.
-        mZoomCenterX = mWebView.getViewWidth() * .5f;
-        mZoomCenterY = mWebView.getViewHeight() * .5f;
-        mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
-        mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
-        return startZoomAnimation(mActualScale * zoomMultiplier, 
-            !mWebView.getSettings().getUseFixedViewport());
-    }
-
-    /**
-     * Initiates an animated zoom of the WebView.
-     *
-     * @return true if the new scale triggered an animation and false otherwise.
-     */
-    public boolean startZoomAnimation(float scale, boolean reflowText) {
-        mInitialZoomOverview = false;
-        float oldScale = mActualScale;
-        mInitialScrollX = mWebView.getScrollX();
-        mInitialScrollY = mWebView.getScrollY();
-
-        // snap to reading level scale if it is close
-        if (!exceedsMinScaleIncrement(scale, getReadingLevelScale())) {
-            scale = getReadingLevelScale();
-        }
-
-        setZoomScale(scale, reflowText);
-
-        if (oldScale != mActualScale) {
-            if (mHardwareAccelerated) {
-                mInHWAcceleratedZoom = true;
-            }
-            // use mZoomPickerScale to see zoom preview first
-            mZoomStart = SystemClock.uptimeMillis();
-            mInvInitialZoomScale = 1.0f / oldScale;
-            mInvFinalZoomScale = 1.0f / mActualScale;
-            mZoomScale = mActualScale;
-            mWebView.onFixedLengthZoomAnimationStart();
-            mWebView.invalidate();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * This method is called by the WebView's drawing code when a fixed length zoom
-     * animation is occurring. Its purpose is to animate the zooming of the canvas
-     * to the desired scale which was specified in startZoomAnimation(...).
-     *
-     * A fixed length animation begins when startZoomAnimation(...) is called and
-     * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that
-     * interval each time the WebView draws it calls this function which is 
-     * responsible for generating the animation.
-     *
-     * Additionally, the WebView can check to see if such an animation is currently
-     * in progress by calling isFixedLengthAnimationInProgress().
-     */
-    public void animateZoom(Canvas canvas) {
-        mInitialZoomOverview = false;
-        if (mZoomScale == 0) {
-            Log.w(LOGTAG, "A WebView is attempting to perform a fixed length "
-                    + "zoom animation when no zoom is in progress");
-            // Now that we've logged about it, go ahead and just recover
-            mInHWAcceleratedZoom = false;
-            return;
-        }
-
-        float zoomScale;
-        int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
-        if (interval < ZOOM_ANIMATION_LENGTH) {
-            float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
-            zoomScale = 1.0f / (mInvInitialZoomScale
-                    + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
-            mWebView.invalidate();
-        } else {
-            zoomScale = mZoomScale;
-            // set mZoomScale to be 0 as we have finished animating
-            mZoomScale = 0;
-            mWebView.onFixedLengthZoomAnimationEnd();
-        }
-        // calculate the intermediate scroll position. Since we need to use
-        // zoomScale, we can't use the WebView's pinLocX/Y functions directly.
-        float scale = zoomScale * mInvInitialZoomScale;
-        int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) - mZoomCenterX);
-        tx = -WebViewClassic.pinLoc(tx, mWebView.getViewWidth(), Math.round(mWebView.getContentWidth()
-                * zoomScale)) + mWebView.getScrollX();
-        int titleHeight = mWebView.getTitleHeight();
-        int ty = Math.round(scale
-                * (mInitialScrollY + mZoomCenterY - titleHeight)
-                - (mZoomCenterY - titleHeight));
-        ty = -(ty <= titleHeight ? Math.max(ty, 0) : WebViewClassic.pinLoc(ty
-                - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight()
-                * zoomScale)) + titleHeight) + mWebView.getScrollY();
-
-        if (mHardwareAccelerated) {
-            mWebView.updateScrollCoordinates(mWebView.getScrollX() - tx, mWebView.getScrollY() - ty);
-            // By adding webView matrix, we need to offset the canvas a bit
-            // to make the animation smooth.
-            canvas.translate(tx, ty);
-            setZoomScale(zoomScale, false);
-
-            if (mZoomScale == 0) {
-                // We've reached the end of the zoom animation.
-                mInHWAcceleratedZoom = false;
-
-                // Ensure that the zoom level is pushed to WebCore. This has not
-                // yet occurred because we prevent it from happening while
-                // mInHWAcceleratedZoom is true.
-                mWebView.sendViewSizeZoom(false);
-            }
-        } else {
-            canvas.translate(tx, ty);
-            canvas.scale(zoomScale, zoomScale);
-        }
-    }
-
-    public boolean isZoomAnimating() {
-        return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
-    }
-
-    public boolean isFixedLengthAnimationInProgress() {
-        return mZoomScale != 0 || mInHWAcceleratedZoom;
-    }
-
-    public void updateDoubleTapZoom(int doubleTapZoom) {
-        boolean zoomIn = (mTextWrapScale - mActualScale) < .1f;
-        mDoubleTapZoomFactor = doubleTapZoom / 100.0f;
-        mTextWrapScale = getReadingLevelScale();
-        float newScale = zoomIn ? mTextWrapScale
-                : Math.min(mTextWrapScale, mActualScale);
-        setZoomScale(newScale, true, true);
-    }
-
-    public void refreshZoomScale(boolean reflowText) {
-        setZoomScale(mActualScale, reflowText, true);
-    }
-
-    public void setZoomScale(float scale, boolean reflowText) {
-        setZoomScale(scale, reflowText, false);
-    }
-
-    private void setZoomScale(float scale, boolean reflowText, boolean force) {
-        final boolean isScaleLessThanMinZoom = scale < mMinZoomScale;
-        scale = computeScaleWithLimits(scale);
-
-        // determine whether or not we are in the zoom overview mode
-        if (isScaleLessThanMinZoom && mMinZoomScale < mDefaultScale) {
-            mInZoomOverview = true;
-        } else {
-            mInZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
-        }
-
-        if (reflowText && !mWebView.getSettings().getUseFixedViewport()) {
-            mTextWrapScale = scale;
-        }
-
-        if (scale != mActualScale || force) {
-            float oldScale = mActualScale;
-            float oldInvScale = mInvActualScale;
-
-            if (scale != mActualScale && !mPinchToZoomAnimating) {
-                mCallbackProxy.onScaleChanged(mActualScale, scale);
-            }
-
-            mActualScale = scale;
-            mInvActualScale = 1 / scale;
-
-            if (!mWebView.drawHistory() && !mInHWAcceleratedZoom) {
-
-                // If history Picture is drawn, don't update scroll. They will
-                // be updated when we get out of that mode.
-                // update our scroll so we don't appear to jump
-                // i.e. keep the center of the doc in the center of the view
-                // If this is part of a zoom on a HW accelerated canvas, we
-                // have already updated the scroll so don't do it again.
-                int oldX = mWebView.getScrollX();
-                int oldY = mWebView.getScrollY();
-                float ratio = scale * oldInvScale;
-                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
-                float sy = ratio * oldY + (ratio - 1)
-                        * (mZoomCenterY - mWebView.getTitleHeight());
-
-                // Scale all the child views
-                mWebView.mViewManager.scaleAll();
-
-                // as we don't have animation for scaling, don't do animation
-                // for scrolling, as it causes weird intermediate state
-                int scrollX = mWebView.pinLocX(Math.round(sx));
-                int scrollY = mWebView.pinLocY(Math.round(sy));
-                if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
-                    // the scroll position is adjusted at the beginning of the
-                    // zoom animation. But we want to update the WebKit at the
-                    // end of the zoom animation. See comments in onScaleEnd().
-                    mWebView.sendOurVisibleRect();
-                }
-            }
-
-            // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
-            // event to be sent to WebKit
-            mWebView.sendViewSizeZoom(reflowText);
-        }
-    }
-
-    public boolean isDoubleTapEnabled() {
-        WebSettings settings = mWebView.getSettings();
-        return settings != null && settings.getUseWideViewPort();
-    }
-
-    /**
-     * The double tap gesture can result in different behaviors depending on the
-     * content that is tapped.
-     *
-     * (1) PLUGINS: If the taps occur on a plugin then we maximize the plugin on
-     * the screen. If the plugin is already maximized then zoom the user into
-     * overview mode.
-     *
-     * (2) HTML/OTHER: If the taps occur outside a plugin then the following
-     * heuristic is used.
-     *   A. If the current text wrap scale differs from newly calculated and the
-     *      layout algorithm specifies the use of NARROW_COLUMNS, then fit to
-     *      column by reflowing the text.
-     *   B. If the page is not in overview mode then change to overview mode.
-     *   C. If the page is in overmode then change to the default scale.
-     */
-    public void handleDoubleTap(float lastTouchX, float lastTouchY) {
-        // User takes action, set initial zoom overview to false.
-        mInitialZoomOverview = false;
-        WebSettingsClassic settings = mWebView.getSettings();
-        if (!isDoubleTapEnabled()) {
-            return;
-        }
-
-        setZoomCenter(lastTouchX, lastTouchY);
-        mAnchorX = mWebView.viewToContentX((int) lastTouchX + mWebView.getScrollX());
-        mAnchorY = mWebView.viewToContentY((int) lastTouchY + mWebView.getScrollY());
-        settings.setDoubleTapToastCount(0);
-
-        // remove the zoom control after double tap
-        dismissZoomPicker();
-
-        final float newTextWrapScale;
-        if (settings.getUseFixedViewport()) {
-            newTextWrapScale = Math.max(mActualScale, getReadingLevelScale());
-        } else {
-            newTextWrapScale = mActualScale;
-        }
-        final boolean firstTimeReflow = !exceedsMinScaleIncrement(mActualScale, mTextWrapScale);
-        if (firstTimeReflow || mInZoomOverview) {
-            // In case first time reflow or in zoom overview mode, let reflow and zoom
-            // happen at the same time.
-            mTextWrapScale = newTextWrapScale;
-        }
-        if (settings.isNarrowColumnLayout()
-                && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)
-                && !firstTimeReflow
-                && !mInZoomOverview) {
-            // Reflow only.
-            mTextWrapScale = newTextWrapScale;
-            refreshZoomScale(true);
-        } else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
-            // Reflow, if necessary.
-            if (mTextWrapScale > getReadingLevelScale()) {
-                mTextWrapScale = getReadingLevelScale();
-                refreshZoomScale(true);
-            }
-            zoomToOverview();
-        } else {
-            zoomToReadingLevel();
-        }
-    }
-
-    private void setZoomOverviewWidth(int width) {
-        if (width == 0) {
-            mZoomOverviewWidth = WebViewClassic.DEFAULT_VIEWPORT_WIDTH;
-        } else {
-            mZoomOverviewWidth = width;
-        }
-        mInvZoomOverviewWidth = 1.0f / width;
-    }
-
-    /* package */ float getZoomOverviewScale() {
-        return mWebView.getViewWidth() * mInvZoomOverviewWidth;
-    }
-
-    public boolean isInZoomOverview() {
-        return mInZoomOverview;
-    }
-
-    private void zoomToOverview() {
-        // Force the titlebar fully reveal in overview mode
-        int scrollY = mWebView.getScrollY();
-        if (scrollY < mWebView.getTitleHeight()) {
-            mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
-        }
-        startZoomAnimation(getZoomOverviewScale(), 
-            !mWebView.getSettings().getUseFixedViewport());
-    }
-
-    private void zoomToReadingLevel() {
-        final float readingScale = getReadingLevelScale();
-
-        int left = mWebView.getBlockLeftEdge(mAnchorX, mAnchorY, readingScale);
-        if (left != WebViewClassic.NO_LEFTEDGE) {
-            // add a 5pt padding to the left edge.
-            int viewLeft = mWebView.contentToViewX(left < 5 ? 0 : (left - 5))
-                    - mWebView.getScrollX();
-            // Re-calculate the zoom center so that the new scroll x will be
-            // on the left edge.
-            if (viewLeft > 0) {
-                mZoomCenterX = viewLeft * readingScale / (readingScale - mActualScale);
-            } else {
-                mWebView.getWebView().scrollBy(viewLeft, 0);
-                mZoomCenterX = 0;
-            }
-        }
-        startZoomAnimation(readingScale,
-            !mWebView.getSettings().getUseFixedViewport());
-    }
-
-    public void updateMultiTouchSupport(Context context) {
-        // check the preconditions
-        assert mWebView.getSettings() != null;
-
-        final WebSettings settings = mWebView.getSettings();
-        final PackageManager pm = context.getPackageManager();
-        mSupportMultiTouch = 
-                (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
-                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT))
-                && settings.supportZoom() && settings.getBuiltInZoomControls();
-        mAllowPanAndScale =
-                pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
-                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
-
-        if (mSupportMultiTouch && (mScaleDetector == null)) {
-            mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener());
-        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
-            mScaleDetector = null;
-        }
-    }
-
-    public boolean supportsMultiTouchZoom() {
-        return mSupportMultiTouch;
-    }
-
-    public boolean supportsPanDuringZoom() {
-        return mAllowPanAndScale;
-    }
-
-    /**
-     * Notifies the caller that the ZoomManager is requesting that scale related
-     * updates should not be sent to webkit. This can occur in cases where the
-     * ZoomManager is performing an animation and does not want webkit to update
-     * until the animation is complete.
-     *
-     * @return true if scale related updates should not be sent to webkit and
-     *         false otherwise.
-     */
-    public boolean isPreventingWebkitUpdates() {
-        // currently only animating a multi-touch zoom and fixed length
-        // animations prevent updates, but others can add their own conditions
-        // to this method if necessary.
-        return isZoomAnimating();
-    }
-
-    public ScaleGestureDetector getScaleGestureDetector() {
-        return mScaleDetector;
-    }
-
-    private class FocusMovementQueue {
-        private static final int QUEUE_CAPACITY = 5;
-        private float[] mQueue;
-        private float mSum;
-        private int mSize;
-        private int mIndex;
-
-        FocusMovementQueue() {
-            mQueue = new float[QUEUE_CAPACITY];
-            mSize = 0;
-            mSum = 0;
-            mIndex = 0;
-        }
-
-        private void clear() {
-            mSize = 0;
-            mSum = 0;
-            mIndex = 0;
-            for (int i = 0; i < QUEUE_CAPACITY; ++i) {
-                mQueue[i] = 0;
-            }
-        }
-
-        private void add(float focusDelta) {
-            mSum += focusDelta;
-            if (mSize < QUEUE_CAPACITY) {  // fill up the queue.
-                mSize++;
-            } else {  // circulate the queue.
-                mSum -= mQueue[mIndex];
-            }
-            mQueue[mIndex] = focusDelta;
-            mIndex = (mIndex + 1) % QUEUE_CAPACITY;
-        }
-
-        private float getSum() {
-            return mSum;
-        }
-    }
-
-    private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener {
-        private float mAccumulatedSpan;
-
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            mInitialZoomOverview = false;
-            dismissZoomPicker();
-            mFocusMovementQueue.clear();
-            mFocusX = detector.getFocusX();
-            mFocusY = detector.getFocusY();
-            mWebView.mViewManager.startZoom();
-            mWebView.onPinchToZoomAnimationStart();
-            mAccumulatedSpan = 0;
-            return true;
-        }
-
-            // If the user moves the fingers but keeps the same distance between them,
-            // we should do panning only.
-        public boolean isPanningOnly(ScaleGestureDetector detector) {
-            float prevFocusX = mFocusX;
-            float prevFocusY = mFocusY;
-            mFocusX = detector.getFocusX();
-            mFocusY = detector.getFocusY();
-            float focusDelta = (prevFocusX == 0 && prevFocusY == 0) ? 0 :
-                    FloatMath.sqrt((mFocusX - prevFocusX) * (mFocusX - prevFocusX)
-                                   + (mFocusY - prevFocusY) * (mFocusY - prevFocusY));
-            mFocusMovementQueue.add(focusDelta);
-            float deltaSpan = detector.getCurrentSpan() - detector.getPreviousSpan() +
-                    mAccumulatedSpan;
-            final boolean result = mFocusMovementQueue.getSum() > Math.abs(deltaSpan);
-            if (result) {
-                mAccumulatedSpan += deltaSpan;
-            } else {
-                mAccumulatedSpan = 0;
-            }
-            return result;
-        }
-
-        public boolean handleScale(ScaleGestureDetector detector) {
-            float scale = detector.getScaleFactor() * mActualScale;
-
-            // if scale is limited by any reason, don't zoom but do ask
-            // the detector to update the event.
-            boolean isScaleLimited =
-                    isScaleOverLimits(scale) || scale < getZoomOverviewScale();
-
-            // Prevent scaling beyond overview scale.
-            scale = Math.max(computeScaleWithLimits(scale), getZoomOverviewScale());
-
-            if (mPinchToZoomAnimating || willScaleTriggerZoom(scale)) {
-                mPinchToZoomAnimating = true;
-                // limit the scale change per step
-                if (scale > mActualScale) {
-                    scale = Math.min(scale, mActualScale * 1.25f);
-                } else {
-                    scale = Math.max(scale, mActualScale * 0.8f);
-                }
-                scale = computeScaleWithLimits(scale);
-                // if the scale change is too small, regard it as jitter and skip it.
-                if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_WITHOUT_JITTER) {
-                    return isScaleLimited;
-                }
-                setZoomCenter(detector.getFocusX(), detector.getFocusY());
-                setZoomScale(scale, false);
-                mWebView.invalidate();
-                return true;
-            }
-            return isScaleLimited;
-        }
-
-        public boolean onScale(ScaleGestureDetector detector) {
-            if (isPanningOnly(detector) || handleScale(detector)) {
-                mFocusMovementQueue.clear();
-                return true;
-            }
-            return false;
-        }
-
-        public void onScaleEnd(ScaleGestureDetector detector) {
-            if (mPinchToZoomAnimating) {
-                mPinchToZoomAnimating = false;
-                mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
-                mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
-                // don't reflow when zoom in; when zoom out, do reflow if the
-                // new scale is almost minimum scale.
-                boolean reflowNow = !canZoomOut() || (mActualScale <= 0.8 * mTextWrapScale);
-                // force zoom after mPreviewZoomOnly is set to false so that the
-                // new view size will be passed to the WebKit
-                refreshZoomScale(reflowNow &&
-                    !mWebView.getSettings().getUseFixedViewport());
-                // call invalidate() to draw without zoom filter
-                mWebView.invalidate();
-            }
-
-            mWebView.mViewManager.endZoom();
-            mWebView.onPinchToZoomAnimationEnd(detector);
-        }
-    }
-
-    private void sanitizeMinMaxScales() {
-        if (mMinZoomScale > mMaxZoomScale) {
-            Log.w(LOGTAG, "mMinZoom > mMaxZoom!!! " + mMinZoomScale + " > " + mMaxZoomScale,
-                    new Exception());
-            mMaxZoomScale = mMinZoomScale;
-        }
-    }
-
-    public void onSizeChanged(int w, int h, int ow, int oh) {
-        // reset zoom and anchor to the top left corner of the screen
-        // unless we are already zooming
-        if (!isFixedLengthAnimationInProgress()) {
-            int visibleTitleHeight = mWebView.getVisibleTitleHeight();
-            mZoomCenterX = 0;
-            mZoomCenterY = visibleTitleHeight;
-            mAnchorX = mWebView.viewToContentX(mWebView.getScrollX());
-            mAnchorY = mWebView.viewToContentY(visibleTitleHeight + mWebView.getScrollY());
-        }
-
-        // update mMinZoomScale if the minimum zoom scale is not fixed
-        if (!mMinZoomScaleFixed) {
-            // when change from narrow screen to wide screen, the new viewWidth
-            // can be wider than the old content width. We limit the minimum
-            // scale to 1.0f. The proper minimum scale will be calculated when
-            // the new picture shows up.
-            mMinZoomScale = Math.min(1.0f, (float) mWebView.getViewWidth()
-                    / (mWebView.drawHistory() ? mWebView.getHistoryPictureWidth()
-                            : mZoomOverviewWidth));
-            // limit the minZoomScale to the initialScale if it is set
-            if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
-                mMinZoomScale = mInitialScale;
-            }
-            sanitizeMinMaxScales();
-        }
-
-        dismissZoomPicker();
-
-        // onSizeChanged() is called during WebView layout. And any
-        // requestLayout() is blocked during layout. As refreshZoomScale() will
-        // cause its child View to reposition itself through ViewManager's
-        // scaleAll(), we need to post a Runnable to ensure requestLayout().
-        // Additionally, only update the text wrap scale if the width changed.
-        mWebView.getWebView().post(new PostScale(w != ow &&
-            !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
-    }
-
-    private class PostScale implements Runnable {
-        final boolean mUpdateTextWrap;
-        // Remember the zoom overview state right after rotation since
-        // it could be changed between the time this callback is initiated and
-        // the time it's actually run.
-        final boolean mInZoomOverviewBeforeSizeChange;
-        final boolean mInPortraitMode;
-
-        public PostScale(boolean updateTextWrap,
-                         boolean inZoomOverview,
-                         boolean inPortraitMode) {
-            mUpdateTextWrap = updateTextWrap;
-            mInZoomOverviewBeforeSizeChange = inZoomOverview;
-            mInPortraitMode = inPortraitMode;
-        }
-
-        public void run() {
-            if (mWebView.getWebViewCore() != null) {
-                // we always force, in case our height changed, in which case we
-                // still want to send the notification over to webkit.
-                // Keep overview mode unchanged when rotating.
-                float newScale = mActualScale;
-                if (mWebView.getSettings().getUseWideViewPort() &&
-                    mInPortraitMode &&
-                    mInZoomOverviewBeforeSizeChange) {
-                    newScale = getZoomOverviewScale();
-                }
-                setZoomScale(newScale, mUpdateTextWrap, true);
-                // update the zoom buttons as the scale can be changed
-                updateZoomPicker();
-            }
-        }
-    }
-
-    public void updateZoomRange(WebViewCore.ViewState viewState,
-            int viewWidth, int minPrefWidth) {
-        if (viewState.mMinScale == 0) {
-            if (viewState.mMobileSite) {
-                if (minPrefWidth > Math.max(0, viewWidth)) {
-                    mMinZoomScale = (float) viewWidth / minPrefWidth;
-                    mMinZoomScaleFixed = false;
-                } else {
-                    mMinZoomScale = viewState.mDefaultScale;
-                    mMinZoomScaleFixed = true;
-                }
-            } else {
-                mMinZoomScale = mDefaultMinZoomScale;
-                mMinZoomScaleFixed = false;
-            }
-        } else {
-            mMinZoomScale = viewState.mMinScale;
-            mMinZoomScaleFixed = true;
-        }
-        if (viewState.mMaxScale == 0) {
-            mMaxZoomScale = mDefaultMaxZoomScale;
-        } else {
-            mMaxZoomScale = viewState.mMaxScale;
-        }
-        sanitizeMinMaxScales();
-    }
-
-    /**
-     * Updates zoom values when Webkit produces a new picture. This method
-     * should only be called from the UI thread's message handler.
-     *
-     * @return True if zoom value has changed
-     */
-    public boolean onNewPicture(WebViewCore.DrawData drawData) {
-        final int viewWidth = mWebView.getViewWidth();
-        final boolean zoomOverviewWidthChanged = setupZoomOverviewWidth(drawData, viewWidth);
-        final float newZoomOverviewScale = getZoomOverviewScale();
-        WebSettingsClassic settings = mWebView.getSettings();
-        if (zoomOverviewWidthChanged && settings.isNarrowColumnLayout() &&
-            settings.getUseFixedViewport() &&
-            (mInitialZoomOverview || mInZoomOverview)) {
-            // Keep mobile site's text wrap scale unchanged.  For mobile sites,
-            // the text wrap scale is the same as zoom overview scale.
-            if (exceedsMinScaleIncrement(mTextWrapScale, mDefaultScale) ||
-                    exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale)) {
-                mTextWrapScale = getReadingLevelScale();
-            } else {
-                mTextWrapScale = newZoomOverviewScale;
-            }
-        }
-
-        if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
-            mMinZoomScale = newZoomOverviewScale;
-            mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
-            sanitizeMinMaxScales();
-        }
-        // fit the content width to the current view for the first new picture
-        // after first layout.
-        boolean scaleHasDiff = exceedsMinScaleIncrement(newZoomOverviewScale, mActualScale);
-        // Make sure the actual scale is no less than zoom overview scale.
-        boolean scaleLessThanOverview =
-                (newZoomOverviewScale - mActualScale) >= MINIMUM_SCALE_INCREMENT;
-        // Make sure mobile sites are correctly handled since mobile site will
-        // change content width after rotating.
-        boolean mobileSiteInOverview = mInZoomOverview &&
-                !exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale);
-        if (!mWebView.drawHistory() &&
-            ((scaleLessThanOverview && settings.getUseWideViewPort())||
-                ((mInitialZoomOverview || mobileSiteInOverview) &&
-                    scaleHasDiff && zoomOverviewWidthChanged))) {
-            mInitialZoomOverview = false;
-            setZoomScale(newZoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale) &&
-                !mWebView.getSettings().getUseFixedViewport());
-        } else {
-            mInZoomOverview = !scaleHasDiff;
-        }
-        if (drawData.mFirstLayoutForNonStandardLoad && settings.getLoadWithOverviewMode()) {
-            // Set mInitialZoomOverview in case this is the first picture for non standard load,
-            // so next new picture could be forced into overview mode if it's true.
-            mInitialZoomOverview = mInZoomOverview;
-        }
-
-        return scaleHasDiff;
-    }
-
-    /**
-     * Set up correct zoom overview width based on different settings.
-     *
-     * @param drawData webviewcore draw data
-     * @param viewWidth current view width
-     */
-    private boolean setupZoomOverviewWidth(WebViewCore.DrawData drawData, final int viewWidth) {
-        WebSettings settings = mWebView.getSettings();
-        int newZoomOverviewWidth = mZoomOverviewWidth;
-        if (settings.getUseWideViewPort()) {
-            if (drawData.mContentSize.x > 0) {
-                // The webkitDraw for layers will not populate contentSize, and it'll be
-                // ignored for zoom overview width update.
-                newZoomOverviewWidth = Math.min(WebViewClassic.sMaxViewportWidth,
-                    drawData.mContentSize.x);
-            }
-        } else {
-            // If not use wide viewport, use view width as the zoom overview width.
-            newZoomOverviewWidth = Math.round(viewWidth / mDefaultScale);
-        }
-        if (newZoomOverviewWidth != mZoomOverviewWidth) {
-            setZoomOverviewWidth(newZoomOverviewWidth);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Updates zoom values after Webkit completes the initial page layout. It
-     * is called when visiting a page for the first time as well as when the
-     * user navigates back to a page (in which case we may need to restore the
-     * zoom levels to the state they were when you left the page). This method
-     * should only be called from the UI thread's message handler.
-     */
-    public void onFirstLayout(WebViewCore.DrawData drawData) {
-        // precondition check
-        assert drawData != null;
-        assert drawData.mViewState != null;
-        assert mWebView.getSettings() != null;
-
-        WebViewCore.ViewState viewState = drawData.mViewState;
-        final Point viewSize = drawData.mViewSize;
-        updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth);
-        setupZoomOverviewWidth(drawData, mWebView.getViewWidth());
-        final float overviewScale = getZoomOverviewScale();
-        WebSettingsClassic settings = mWebView.getSettings();
-        if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
-            mMinZoomScale = (mInitialScale > 0) ?
-                    Math.min(mInitialScale, overviewScale) : overviewScale;
-            mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
-            sanitizeMinMaxScales();
-        }
-
-        if (!mWebView.drawHistory()) {
-            float scale;
-            if (mInitialScale > 0) {
-                scale = mInitialScale;
-            } else if (viewState.mIsRestored || viewState.mViewScale > 0) {
-                scale = (viewState.mViewScale > 0)
-                    ? viewState.mViewScale : overviewScale;
-                mTextWrapScale = (viewState.mTextWrapScale > 0)
-                    ? viewState.mTextWrapScale : getReadingLevelScale();
-            } else {
-                scale = overviewScale;
-                if (!settings.getUseWideViewPort()
-                    || !settings.getLoadWithOverviewMode()) {
-                    scale = Math.max(mDefaultScale, scale);
-                }
-                if (settings.isNarrowColumnLayout() &&
-                    settings.getUseFixedViewport()) {
-                    // When first layout, reflow using the reading level scale to avoid
-                    // reflow when double tapped.
-                    mTextWrapScale = getReadingLevelScale();
-                }
-            }
-            boolean reflowText = false;
-            if (!viewState.mIsRestored) {
-                if (settings.getUseFixedViewport()) {
-                    // Override the scale only in case of fixed viewport.
-                    scale = Math.max(scale, overviewScale);
-                    mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
-                }
-                reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
-            }
-            mInitialZoomOverview = settings.getLoadWithOverviewMode() &&
-                    !exceedsMinScaleIncrement(scale, overviewScale);
-            setZoomScale(scale, reflowText);
-
-            // update the zoom buttons as the scale can be changed
-            updateZoomPicker();
-        }
-    }
-
-    public void saveZoomState(Bundle b) {
-        b.putFloat("scale", mActualScale);
-        b.putFloat("textwrapScale", mTextWrapScale);
-        b.putBoolean("overview", mInZoomOverview);
-    }
-
-    public void restoreZoomState(Bundle b) {
-        // as getWidth() / getHeight() of the view are not available yet, set up
-        // mActualScale, so that when onSizeChanged() is called, the rest will
-        // be set correctly
-        mActualScale = b.getFloat("scale", 1.0f);
-        mInvActualScale = 1 / mActualScale;
-        mTextWrapScale = b.getFloat("textwrapScale", mActualScale);
-        mInZoomOverview = b.getBoolean("overview");
-    }
-
-    private ZoomControlBase getCurrentZoomControl() {
-        if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
-            if (mWebView.getSettings().getBuiltInZoomControls()) {
-                if ((mEmbeddedZoomControl == null)
-                        && mWebView.getSettings().getDisplayZoomControls()) {
-                    mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
-                }
-                return mEmbeddedZoomControl;
-            } else {
-                if (mExternalZoomControl == null) {
-                    mExternalZoomControl = new ZoomControlExternal(mWebView);
-                }
-                return mExternalZoomControl;
-            }
-        }
-        return null;
-    }
-
-    public void invokeZoomPicker() {
-        ZoomControlBase control = getCurrentZoomControl();
-        if (control != null) {
-            control.show();
-        }
-    }
-
-    public void dismissZoomPicker() {
-        ZoomControlBase control = getCurrentZoomControl();
-        if (control != null) {
-            control.hide();
-        }
-    }
-
-    public boolean isZoomPickerVisible() {
-        ZoomControlBase control = getCurrentZoomControl();
-        return (control != null) ? control.isVisible() : false;
-    }
-
-    public void updateZoomPicker() {
-        ZoomControlBase control = getCurrentZoomControl();
-        if (control != null) {
-            control.update();
-        }
-    }
-
-    /**
-     * The embedded zoom control intercepts touch events and automatically stays
-     * visible. The external control needs to constantly refresh its internal
-     * timer to stay visible.
-     */
-    public void keepZoomPickerVisible() {
-        ZoomControlBase control = getCurrentZoomControl();
-        if (control != null && control == mExternalZoomControl) {
-            control.show();
-        }
-    }
-
-    public View getExternalZoomPicker() {
-        ZoomControlBase control = getCurrentZoomControl();
-        if (control != null && control == mExternalZoomControl) {
-            return mExternalZoomControl.getControls();
-        } else {
-            return null;
-        }
-    }
-
-    public void setHardwareAccelerated() {
-        mHardwareAccelerated = true;
-    }
-
-    /**
-     * OnPageFinished called by webview when a page is fully loaded.
-     */
-    /* package*/ void onPageFinished(String url) {
-        // Turn off initial zoom overview flag when a page is fully loaded.
-        mInitialZoomOverview = false;
-    }
-}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 636641d..9dbad7a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2088,8 +2088,8 @@
             mRecycler.markChildrenDirty();
         }
 
-        if (mFastScroller != null && mItemCount != mOldItemCount) {
-            mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
+        if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) {
+            mFastScroller.onItemCountChanged(mItemCount);
         }
 
         layoutChildren();
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d2528f9..aa3a25d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -800,6 +800,8 @@
 
     private boolean isOffsetVisible(int offset) {
         Layout layout = mTextView.getLayout();
+        if (layout == null) return false;
+
         final int line = layout.getLineForOffset(offset);
         final int lineBottom = layout.getLineBottom(line);
         final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset);
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index a70bcdb..01ac8fd 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.IntProperty;
 import android.util.MathUtils;
@@ -42,7 +43,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewGroupOverlay;
 import android.widget.AbsListView.OnScrollListener;
-
 import com.android.internal.R;
 
 /**
@@ -177,6 +177,9 @@
      */
     private int mState;
 
+    /** Whether the preview image is visible. */
+    private boolean mShowingPreview;
+
     private BaseAdapter mListAdapter;
     private SectionIndexer mSectionIndexer;
 
@@ -442,7 +445,7 @@
         updateLayout();
     }
 
-    public void onItemCountChanged(int oldTotalItemCount, int totalItemCount) {
+    public void onItemCountChanged(int totalItemCount) {
         final int visibleItemCount = mList.getChildCount();
         final boolean hasMoreItems = totalItemCount - visibleItemCount > 0;
         if (hasMoreItems && mState != STATE_DRAGGING) {
@@ -770,6 +773,8 @@
         mDecorAnimation = new AnimatorSet();
         mDecorAnimation.playTogether(fadeOut, slideOut);
         mDecorAnimation.start();
+
+        mShowingPreview = false;
     }
 
     /**
@@ -791,6 +796,8 @@
         mDecorAnimation = new AnimatorSet();
         mDecorAnimation.playTogether(fadeIn, fadeOut, slideIn);
         mDecorAnimation.start();
+
+        mShowingPreview = false;
     }
 
     /**
@@ -810,6 +817,8 @@
         mDecorAnimation = new AnimatorSet();
         mDecorAnimation.playTogether(fadeIn, slideIn);
         mDecorAnimation.start();
+
+        mShowingPreview = true;
     }
 
     private void postAutoHide() {
@@ -983,9 +992,10 @@
         if (mCurrentSection != sectionIndex) {
             mCurrentSection = sectionIndex;
 
-            if (transitionPreviewLayout(sectionIndex)) {
+            final boolean hasPreview = transitionPreviewLayout(sectionIndex);
+            if (!mShowingPreview && hasPreview) {
                 transitionToDragging();
-            } else {
+            } else if (mShowingPreview && !hasPreview) {
                 transitionToVisible();
             }
         }
@@ -1073,7 +1083,7 @@
 
         mPreviewAnimation.start();
 
-        return (text != null && text.length() > 0);
+        return !TextUtils.isEmpty(text);
     }
 
     /**
@@ -1096,12 +1106,14 @@
         final float thumbMiddle = position * range + offset;
         thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
 
+        final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0;
+
         // Center the preview on the thumb, constrained to the list bounds.
         final ImageView previewImage = mPreviewImage;
         final float previewHalfHeight = previewImage.getHeight() / 2f;
         final float minP = top + previewHalfHeight;
         final float maxP = bottom - previewHalfHeight;
-        final float previewMiddle = MathUtils.constrain(thumbMiddle, minP, maxP);
+        final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
         final float previewTop = previewMiddle - previewHalfHeight;
         previewImage.setTranslationY(previewTop);
 
@@ -1183,7 +1195,19 @@
                     / positionsInSection;
         }
 
-        return (section + posWithinSection) / sectionCount;
+        float result = (section + posWithinSection) / sectionCount;
+
+        // Fake out the scroll bar for the last item. Since the section indexer
+        // won't ever actually move the list in this end space, make scrolling
+        // across the last item account for whatever space is remaining.
+        if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) {
+            final View lastChild = mList.getChildAt(visibleItemCount - 1);
+            final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom()
+                    - lastChild.getTop()) / lastChild.getHeight();
+            result += (1 - result) * lastItemVisible;
+        }
+
+        return result;
     }
 
     /**
@@ -1381,7 +1405,10 @@
     }
 
     private boolean isPointInsideY(float y) {
-        return y >= mThumbImage.getTop() && y <= mThumbImage.getBottom();
+        final float offset = mThumbImage.getTranslationY();
+        final float top = mThumbImage.getTop() + offset;
+        final float bottom = mThumbImage.getBottom() + offset;
+        return y >= top && y <= bottom;
     }
 
     /**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 31e5820..cdadcb7 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -512,7 +512,6 @@
         requestLayout();
     }
 
-
     /**
      * The list is empty. Clear everything out.
      */
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 9d90f32..a4f758c 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -97,7 +97,9 @@
                 com.android.internal.R.styleable.Theme_quickContactBadgeOverlay);
         styledAttributes.recycle();
 
-        mQueryHandler = new QueryHandler(mContext.getContentResolver());
+        if (!isInEditMode()) {
+            mQueryHandler = new QueryHandler(mContext.getContentResolver());
+        }
         setOnClickListener(this);
     }
 
@@ -204,7 +206,7 @@
     public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
         mContactEmail = emailAddress;
         mExtras = extras;
-        if (!lazyLookup) {
+        if (!lazyLookup && mQueryHandler != null) {
             mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
                     Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
                     EMAIL_LOOKUP_PROJECTION, null, null, null);
@@ -244,7 +246,7 @@
     public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
         mContactPhone = phoneNumber;
         mExtras = extras;
-        if (!lazyLookup) {
+        if (!lazyLookup && mQueryHandler != null) {
             mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
                     PHONE_LOOKUP_PROJECTION, null, null, null);
@@ -267,12 +269,12 @@
         if (mContactUri != null) {
             QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
                     QuickContact.MODE_LARGE, mExcludeMimes);
-        } else if (mContactEmail != null) {
+        } else if (mContactEmail != null && mQueryHandler != null) {
             extras.putString(EXTRA_URI_CONTENT, mContactEmail);
             mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
                     Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
                     EMAIL_LOOKUP_PROJECTION, null, null, null);
-        } else if (mContactPhone != null) {
+        } else if (mContactPhone != null && mQueryHandler != null) {
             extras.putString(EXTRA_URI_CONTENT, mContactPhone);
             mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 76de3459..d28be7b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4874,8 +4874,10 @@
 
     @Override
     public boolean hasOverlappingRendering() {
+        // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
         return ((getBackground() != null && getBackground().getCurrent() != null)
-                || mText instanceof Spannable || hasSelection());
+                || mText instanceof Spannable || hasSelection()
+                || isHorizontalFadingEdgeEnabled());
     }
 
     /**
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 46f9adf..371b2f1 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -84,9 +84,6 @@
      */
     public static final int LENGTH_LONG = 1;
 
-    /** @hide */
-    public static final int LENGTH_INFINITE = 2;
-
     final Context mContext;
     final TN mTN;
     int mDuration;
@@ -304,61 +301,6 @@
         tv.setText(s);
     }
 
-    /** @hide */
-    public static Toast makeBar(Context context, int resId, int duration) {
-        return makeBar(context, context.getResources().getText(resId), duration);
-    }
-
-    /** @hide */
-    public static Toast makeBar(Context context, CharSequence text, int duration) {
-        Toast result = new Toast(context);
-
-        LayoutInflater inflate = (LayoutInflater)
-                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        View v = inflate.inflate(com.android.internal.R.layout.toast_bar, null);
-        ((TextView)v.findViewById(android.R.id.message)).setText(text);
-        v.findViewById(android.R.id.button1).setVisibility(View.GONE);
-
-        result.mNextView = v;
-        result.mDuration = duration;
-        result.mTN.mParams.alpha = 0.9f;
-        result.mTN.mParams.windowAnimations = com.android.internal.R.style.Animation_ToastBar;
-
-        return result;
-    }
-
-    /** @hide */
-    public Toast setAction(int resId, Runnable action) {
-        return setAction(mContext.getResources().getText(resId), action);
-    }
-
-    /** @hide */
-    public Toast setAction(CharSequence actionText, final Runnable action) {
-        if (mNextView != null) {
-            TextView text1 = (TextView)mNextView.findViewById(android.R.id.text1);
-            View button1 =  mNextView.findViewById(android.R.id.button1);
-            if (text1 != null && button1 != null) {
-                text1.setText(actionText);
-                button1.setVisibility(View.VISIBLE);
-                button1.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        if (action != null) {
-                            action.run();
-                        }
-                    }});
-                return setInteractive(true);
-            }
-        }
-        throw new RuntimeException("This Toast was not created with Toast.makeBar()");
-    }
-
-    /** @hide */
-    public Toast setInteractive(boolean interactive) {
-        mTN.setInteractive(interactive);
-        return this;
-    }
-
     // =======================================================================================
     // All the gunk below is the interaction with the Notification Service, which handles
     // the proper ordering of these system-wide.
@@ -415,16 +357,9 @@
             params.windowAnimations = com.android.internal.R.style.Animation_Toast;
             params.type = WindowManager.LayoutParams.TYPE_TOAST;
             params.setTitle("Toast");
-            setInteractive(false);
-        }
-
-        private void setInteractive(boolean interactive) {
-            mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | (interactive
-                            ? (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH)
-                            : WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
         }
 
         /**
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 061bb00..b152297 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -90,7 +90,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_USER_PRESENT);
-        getContext().registerReceiver(mReceiver, filter);
+        getContext().registerReceiver(mReceiver, filter, null, mHandler);
 
         if (mAutoStart) {
             // Automatically start when requested
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 43bd735..40a705c 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -79,7 +79,7 @@
         letter.setTextSize(300);
         letter.setTextColor(0xFFFFFFFF);
         letter.setGravity(Gravity.CENTER);
-        letter.setText(String.valueOf(Build.VERSION.RELEASE).substring(0, 1));
+        letter.setText(String.valueOf(Build.ID).substring(0, 1));
 
         final int p = (int)(4 * metrics.density);
 
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 430e43a..222e446 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -2918,29 +2918,38 @@
             }
         }
 
-        public void clearCurrentOwner(Object owner) {
+        public void clearCurrentOwner(Object owner, boolean silently) {
             if (mOwner == owner) {
-                mOwner = null;
                 mProc.decActiveServices(mName);
                 if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
                         || mExecState != STATE_NOTHING) {
                     long now = SystemClock.uptimeMillis();
                     if (mStartedState != STATE_NOTHING) {
-                        Slog.wtfStack(TAG, "Service owner " + owner + " cleared while started: pkg="
-                                + mPackage + " service=" + mName + " proc=" + mProc);
+                        if (!silently) {
+                            Slog.wtfStack(TAG, "Service owner " + owner
+                                    + " cleared while started: pkg=" + mPackage + " service="
+                                    + mName + " proc=" + mProc);
+                        }
                         setStarted(false, 0, now);
                     }
                     if (mBoundState != STATE_NOTHING) {
-                        Slog.wtfStack(TAG, "Service owner " + owner + " cleared while bound: pkg="
-                                + mPackage + " service=" + mName + " proc=" + mProc);
+                        if (!silently) {
+                            Slog.wtfStack(TAG, "Service owner " + owner
+                                    + " cleared while bound: pkg=" + mPackage + " service="
+                                    + mName + " proc=" + mProc);
+                        }
                         setBound(false, 0, now);
                     }
                     if (mExecState != STATE_NOTHING) {
-                        Slog.wtfStack(TAG, "Service owner " + owner + " cleared while exec: pkg="
-                                + mPackage + " service=" + mName + " proc=" + mProc);
+                        if (!silently) {
+                            Slog.wtfStack(TAG, "Service owner " + owner
+                                    + " cleared while exec: pkg=" + mPackage + " service="
+                                    + mName + " proc=" + mProc);
+                        }
                         setExecuting(false, 0, now);
                     }
                 }
+                mOwner = null;
             }
         }
 
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 45a38be..63ff5a0 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -43,4 +43,5 @@
     oneway void showAssistant();
     oneway void dispatch(in MotionEvent event);
     oneway void launchCamera();
+    oneway void onBootCompleted();
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 12ced68..cc332b3 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -74,4 +74,5 @@
     boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
     boolean setInputMethodEnabled(String id, boolean enabled);
     oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+    int getInputMethodWindowVisibleHeight();
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 23ed019..44e7ec1 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -160,8 +160,7 @@
     public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
         View actionView = item.getActionView();
         if (actionView == null || item.hasCollapsibleActionView()) {
-            // Don't recycle existing item views for action buttons; it interferes with transitions.
-            actionView = super.getItemView(item, null, parent);
+            actionView = super.getItemView(item, convertView, parent);
         }
         actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
 
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 0bdf90d..063468d 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -183,7 +183,9 @@
             mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
 
             mHasMeasurements = false;
-            forceLayout();
+
+            requestLayout();
+            invalidate();
         }
     }
 
@@ -192,7 +194,9 @@
             mTextPaint.setTypeface(typeface);
 
             mHasMeasurements = false;
-            forceLayout();
+
+            requestLayout();
+            invalidate();
         }
     }
 
@@ -201,7 +205,9 @@
             mAlignment = textAlignment;
 
             mHasMeasurements = false;
-            forceLayout();
+
+            requestLayout();
+            invalidate();
         }
     }
 
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index e09fcff..5983120 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -60,7 +60,6 @@
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
 	android_os_Debug.cpp \
-	android_os_FileUtils.cpp \
 	android_os_MemoryFile.cpp \
 	android_os_MessageQueue.cpp \
 	android_os_Parcel.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8518101..1cce263 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -142,7 +142,6 @@
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_Trace(JNIEnv* env);
 extern int register_android_os_FileObserver(JNIEnv *env);
-extern int register_android_os_FileUtils(JNIEnv *env);
 extern int register_android_os_UEventObserver(JNIEnv* env);
 extern int register_android_os_MemoryFile(JNIEnv* env);
 extern int register_android_net_LocalSocketImpl(JNIEnv* env);
@@ -1023,7 +1022,8 @@
     void** args = (void**) malloc(3 * sizeof(void*));   // javaThreadShell must free
     int result;
 
-    assert(threadName != NULL);
+    if (!threadName)
+        threadName = "unnamed thread";
 
     args[0] = (void*) entryFunction;
     args[1] = userData;
@@ -1169,7 +1169,6 @@
     REG_JNI(register_android_database_SQLiteDebug),
     REG_JNI(register_android_os_Debug),
     REG_JNI(register_android_os_FileObserver),
-    REG_JNI(register_android_os_FileUtils),
     REG_JNI(register_android_os_MessageQueue),
     REG_JNI(register_android_os_SELinux),
     REG_JNI(register_android_os_Trace),
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 8cb152d..0a8eeab 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -174,6 +174,12 @@
 static jfieldID gRegion_nativeInstanceID;
 static jmethodID gRegion_constructorMethodID;
 
+static jclass    gByte_class;
+static jobject   gVMRuntime;
+static jclass    gVMRuntime_class;
+static jmethodID gVMRuntime_newNonMovableArray;
+static jmethodID gVMRuntime_addressOf;
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
@@ -529,8 +535,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-extern "C" jbyte* jniGetNonMovableArrayElements(C_JNIEnv* env, jarray arrayObj);
-
 jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                              SkColorTable* ctable) {
     Sk64 size64 = bitmap->getSize64();
@@ -539,12 +543,12 @@
                           "bitmap size exceeds 32bits");
         return NULL;
     }
-
     size_t size = size64.get32();
-    jbyteArray arrayObj = env->NewByteArray(size);
+    jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
+                                                             gVMRuntime_newNonMovableArray,
+                                                             gByte_class, size);
     if (arrayObj) {
-        // TODO: make this work without jniGetNonMovableArrayElements
-        jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj);
+        jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
         if (addr) {
             SkPixelRef* pr = new AndroidPixelRef(env, (void*) addr, size, arrayObj, ctable);
             bitmap->setPixelRef(pr)->unref();
@@ -601,7 +605,7 @@
 {
     jclass c = env->FindClass(classname);
     SkASSERT(c);
-    return (jclass)env->NewGlobalRef(c);
+    return (jclass) env->NewGlobalRef(c);
 }
 
 static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
@@ -663,5 +667,16 @@
     gRegion_constructorMethodID = env->GetMethodID(gRegion_class, "<init>",
         "(II)V");
 
+    c = env->FindClass("java/lang/Byte");
+    gByte_class = (jclass) env->NewGlobalRef(
+        env->GetStaticObjectField(c, env->GetStaticFieldID(c, "TYPE", "Ljava/lang/Class;")));
+
+    gVMRuntime_class = make_globalref(env, "dalvik/system/VMRuntime");
+    m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
+    gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
+    gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray",
+                                                     "(Ljava/lang/Class;I)Ljava/lang/Object;");
+    gVMRuntime_addressOf = env->GetMethodID(gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
+
     return 0;
 }
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index a0c50fa..92d253f 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -721,7 +721,8 @@
         
         hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
         hb_buffer_set_script(mBuffer, run.script);
-        // Should set language here (for bug 7004056)
+        SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
+        hb_buffer_set_language(mBuffer, hb_language_from_string(langString.c_str(), -1));
         hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
 
         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 4290a6e..793d1bf 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -220,9 +220,9 @@
     receiver->decStrong((void*)nativeInitSensorEventQueue);
 }
 
-static jint nativeFlushSensor(JNIEnv *env, jclass clazz, jint eventQ, jint handle) {
+static jint nativeFlushSensor(JNIEnv *env, jclass clazz, jint eventQ) {
     sp<Receiver> receiver(reinterpret_cast<Receiver *>(eventQ));
-    return receiver->getSensorEventQueue()->flushSensor(handle);
+    return receiver->getSensorEventQueue()->flush();
 }
 
 //----------------------------------------------------------------------------
@@ -255,7 +255,7 @@
             (void*)nativeDestroySensorEventQueue },
 
     {"nativeFlushSensor",
-            "(II)I",
+            "(I)I",
             (void*)nativeFlushSensor },
 };
 
diff --git a/core/jni/android_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
deleted file mode 100644
index d1245da..0000000
--- a/core/jni/android_os_FileUtils.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/* //device/libs/android_runtime/android_util_Process.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 "FileUtils"
-
-#include <utils/Log.h>
-
-#include <android_runtime/AndroidRuntime.h>
-
-#include "JNIHelp.h"
-
-#include <sys/errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <sys/ioctl.h>
-#include <linux/msdos_fs.h>
-
-namespace android {
-
-jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
-{
-    if (path == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return -1;
-    }
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-    int result = -1;
-    // only if our system supports this ioctl
-    #ifdef VFAT_IOCTL_GET_VOLUME_ID
-    int fd = open(pathStr, O_RDONLY);
-    if (fd >= 0) {
-        result = ioctl(fd, VFAT_IOCTL_GET_VOLUME_ID);
-        close(fd);
-    }
-    #endif
-
-    env->ReleaseStringUTFChars(path, pathStr);
-    return result;
-}
-
-static const JNINativeMethod methods[] = {
-    {"getFatVolumeId",  "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
-};
-
-static const char* const kFileUtilsPathName = "android/os/FileUtils";
-
-int register_android_os_FileUtils(JNIEnv* env)
-{
-    return AndroidRuntime::registerNativeMethods(
-        env, kFileUtilsPathName,
-        methods, NELEM(methods));
-}
-
-}
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index b0c26c51..b3b0049 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -183,7 +183,7 @@
             if (array) {
                 releasePointer(_env, array, buf, 0);
             }
-            buf = buf + offset;
+            buf = (char*)buf + offset;
         } else {
             jniThrowException(_env, "java/lang/IllegalArgumentException",
                               "Must use a native order direct Buffer");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 59b7c23..183959f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -230,7 +230,7 @@
     <protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
     <protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
-    <protected-brodcast
+    <protected-broadcast
             android:name="android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED" />
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE" />
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
@@ -773,7 +773,8 @@
         android:description="@string/permdesc_bluetoothAdmin"
         android:label="@string/permlab_bluetoothAdmin" />
 
-    <!-- Allows applications to pair bluetooth devices without user interaction -->
+    <!-- Allows applications to pair bluetooth devices without user interaction.
+         This is not available to third party applications. -->
     <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
         android:permissionGroup="android.permission-group.BLUETOOTH_NETWORK"
         android:protectionLevel="system|signature"
@@ -1110,7 +1111,8 @@
         android:description="@string/permdesc_use_sip"
         android:label="@string/permlab_use_sip" />
 
-    <!-- Allows an application to request CallHandlerService implementations. -->
+    <!-- Allows an application to request CallHandlerService implementations.
+         @hide -->
     <permission android:name="android.permission.BIND_CALL_SERVICE"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
         android:protectionLevel="system|signature"
@@ -1956,13 +1958,15 @@
 
     <!-- Must be required by a {@link android.nfc.cardemulation.HostApduService}
          or {@link android.nfc.cardemulation.OffHostApduService} to ensure that only
-         the system can bind to it. -->
+         the system can bind to it.
+         @hide -->
     <permission android:name="android.permission.BIND_NFC_SERVICE"
         android:label="@string/permlab_bindNfcService"
         android:description="@string/permdesc_bindNfcService"
         android:protectionLevel="signature" />
 
-    <!-- Must be required by the PrintSpooler to ensure that only the system can bind to it. -->
+    <!-- Must be required by the PrintSpooler to ensure that only the system can bind to it.
+         @hide -->
     <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
         android:label="@string/permlab_bindPrintSpoolerService"
         android:description="@string/permdesc_bindPrintSpoolerService"
@@ -1997,7 +2001,8 @@
         android:protectionLevel="signature" />
 
     <!-- Required to add or remove another application as a device admin.
-         <p/>Not for use by third-party applications. -->
+         <p>Not for use by third-party applications.
+         @hide -->
     <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
         android:label="@string/permlab_manageDeviceAdmins"
         android:description="@string/permdesc_manageDeviceAdmins"
diff --git a/core/res/res/anim/toast_bar_enter.xml b/core/res/res/anim/toast_bar_enter.xml
deleted file mode 100644
index 5c0dfcf..0000000
--- a/core/res/res/anim/toast_bar_enter.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <translate android:fromYDelta="10%" android:toYDelta="0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:duration="@android:integer/config_shortAnimTime"/>
-    <alpha android:fromAlpha="0.5" android:toAlpha="1.0"
-            android:interpolator="@interpolator/decelerate_cubic"
-            android:duration="@android:integer/config_shortAnimTime" />
-</set>
diff --git a/core/res/res/anim/toast_bar_exit.xml b/core/res/res/anim/toast_bar_exit.xml
deleted file mode 100644
index 4e3b7da..0000000
--- a/core/res/res/anim/toast_bar_exit.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <translate android:fromYDelta="0" android:toYDelta="10%"
-            android:interpolator="@interpolator/accelerate_quint"
-            android:duration="@android:integer/config_shortAnimTime"/>
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-            android:interpolator="@interpolator/accelerate_cubic"
-            android:duration="@android:integer/config_shortAnimTime"/>
-</set>
diff --git a/core/res/res/drawable-hdpi/cling_arrow_up.png b/core/res/res/drawable-hdpi/cling_arrow_up.png
new file mode 100644
index 0000000..8ef2050
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cling_arrow_up.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cling_bg.9.png b/core/res/res/drawable-hdpi/cling_bg.9.png
new file mode 100644
index 0000000..36fbfc8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cling_bg.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cling_button_normal.9.png b/core/res/res/drawable-hdpi/cling_button_normal.9.png
new file mode 100644
index 0000000..e308382
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cling_button_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cling_button_pressed.9.png b/core/res/res/drawable-hdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..4f9ca6f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_adb_am.png b/core/res/res/drawable-hdpi/stat_sys_adb_am.png
index 382557e..dad614c 100644
--- a/core/res/res/drawable-hdpi/stat_sys_adb_am.png
+++ b/core/res/res/drawable-hdpi/stat_sys_adb_am.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_gps_on.png b/core/res/res/drawable-hdpi/stat_sys_gps_on.png
index cb8a1e8..e0f7740 100644
--- a/core/res/res/drawable-hdpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-hdpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/toast_bar_bg.9.png b/core/res/res/drawable-hdpi/toast_bar_bg.9.png
deleted file mode 100644
index 2396b26..0000000
--- a/core/res/res/drawable-hdpi/toast_bar_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/stat_sys_gps_on.png b/core/res/res/drawable-ldpi/stat_sys_gps_on.png
index 8915c59..77776f5 100644
--- a/core/res/res/drawable-ldpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-ldpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cling_arrow_up.png b/core/res/res/drawable-mdpi/cling_arrow_up.png
new file mode 100644
index 0000000..ee6c378
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cling_arrow_up.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cling_bg.9.png b/core/res/res/drawable-mdpi/cling_bg.9.png
new file mode 100644
index 0000000..4c0f139
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cling_bg.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cling_button_normal.9.png b/core/res/res/drawable-mdpi/cling_button_normal.9.png
new file mode 100644
index 0000000..a0b6f97
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cling_button_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cling_button_pressed.9.png b/core/res/res/drawable-mdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..986e669
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_adb_am.png b/core/res/res/drawable-mdpi/stat_sys_adb_am.png
index 4380035..5482f34 100644
--- a/core/res/res/drawable-mdpi/stat_sys_adb_am.png
+++ b/core/res/res/drawable-mdpi/stat_sys_adb_am.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_gps_on.png b/core/res/res/drawable-mdpi/stat_sys_gps_on.png
index 2c98972..311a1de 100644
--- a/core/res/res/drawable-mdpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-mdpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/toast_bar_bg.9.png b/core/res/res/drawable-mdpi/toast_bar_bg.9.png
deleted file mode 100644
index 291a936..0000000
--- a/core/res/res/drawable-mdpi/toast_bar_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cling_arrow_up.png b/core/res/res/drawable-xhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..2803155
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cling_arrow_up.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cling_bg.9.png b/core/res/res/drawable-xhdpi/cling_bg.9.png
new file mode 100644
index 0000000..1cb4681
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cling_bg.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cling_button_normal.9.png b/core/res/res/drawable-xhdpi/cling_button_normal.9.png
new file mode 100644
index 0000000..4192563
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cling_button_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/cling_button_pressed.9.png b/core/res/res/drawable-xhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..d3ce469
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_sys_adb_am.png b/core/res/res/drawable-xhdpi/stat_sys_adb_am.png
index 3222a76..e53f498 100644
--- a/core/res/res/drawable-xhdpi/stat_sys_adb_am.png
+++ b/core/res/res/drawable-xhdpi/stat_sys_adb_am.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/stat_sys_gps_on.png b/core/res/res/drawable-xhdpi/stat_sys_gps_on.png
index a7408d4..8a6edfb 100644
--- a/core/res/res/drawable-xhdpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-xhdpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/toast_bar_bg.9.png b/core/res/res/drawable-xhdpi/toast_bar_bg.9.png
deleted file mode 100644
index 1dc4927..0000000
--- a/core/res/res/drawable-xhdpi/toast_bar_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cling_arrow_up.png b/core/res/res/drawable-xxhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..1983f13
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cling_arrow_up.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cling_bg.9.png b/core/res/res/drawable-xxhdpi/cling_bg.9.png
new file mode 100644
index 0000000..7beae03
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cling_bg.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cling_button_normal.9.png b/core/res/res/drawable-xxhdpi/cling_button_normal.9.png
new file mode 100644
index 0000000..e412876
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cling_button_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/cling_button_pressed.9.png b/core/res/res/drawable-xxhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..55e89da
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png b/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png
index e01ad386..d6018dd 100644
--- a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png
+++ b/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/stat_sys_gps_on.png b/core/res/res/drawable-xxhdpi/stat_sys_gps_on.png
index 81fb04a..063f614 100755
--- a/core/res/res/drawable-xxhdpi/stat_sys_gps_on.png
+++ b/core/res/res/drawable-xxhdpi/stat_sys_gps_on.png
Binary files differ
diff --git a/core/res/res/drawable/cling_button.xml b/core/res/res/drawable/cling_button.xml
new file mode 100644
index 0000000..9ce191c
--- /dev/null
+++ b/core/res/res/drawable/cling_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="true"
+          android:drawable="@drawable/cling_button_pressed" />
+    <item
+          android:drawable="@drawable/cling_button_normal" />
+</selector>
diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml
new file mode 100644
index 0000000..f97225e
--- /dev/null
+++ b/core/res/res/layout/immersive_mode_cling.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="12dp"
+    >
+    <LinearLayout
+        android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginTop="4dp"
+        android:padding="1dp"
+        >
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:src="@drawable/cling_arrow_up"
+            />
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@drawable/cling_bg"
+            android:paddingLeft="14dp"
+            android:paddingRight="14dp"
+            android:paddingTop="24dp"
+            android:paddingBottom="24dp">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/immersive_mode_confirmation"
+                android:textColor="#80000000"
+                android:textSize="16sp"
+                />
+        </FrameLayout>
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/ok"
+        android:layout_width="160sp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="right"
+        android:layout_marginTop="18dp"
+        android:gravity="center"
+        android:text="@string/ok"
+        android:background="@drawable/cling_button"
+        />
+
+</LinearLayout>
+
+
diff --git a/core/res/res/layout/input_method.xml b/core/res/res/layout/input_method.xml
index f80d628..79f1ce8 100644
--- a/core/res/res/layout/input_method.xml
+++ b/core/res/res/layout/input_method.xml
@@ -52,4 +52,5 @@
         android:layout_height="wrap_content"
         android:visibility="gone">
     </FrameLayout>
+
 </LinearLayout>
diff --git a/core/res/res/layout/toast_bar.xml b/core/res/res/layout/toast_bar.xml
deleted file mode 100644
index a31d7cb..0000000
--- a/core/res/res/layout/toast_bar.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <LinearLayout
-        android:background="@drawable/toast_bar_bg"
-        android:layout_height="50dp"
-        android:layout_width="match_parent">
-
-        <TextView
-            android:id="@android:id/message"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:ellipsize="end"
-            android:gravity="center_vertical"
-            android:paddingLeft="16dp"
-            android:paddingRight="16dp"
-            android:singleLine="true"
-            android:textColor="@android:color/white"
-            android:textSize="14sp" />
-
-        <LinearLayout
-            android:id="@android:id/button1"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:background="?android:attr/selectableItemBackground"
-            android:clickable="true">
-
-            <View
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:layout_marginBottom="10dp"
-                android:layout_marginRight="12dp"
-                android:layout_marginTop="10dp"
-                android:background="#aaaaaa" />
-
-            <TextView
-                android:id="@android:id/text1"
-                android:textSize="12sp"
-                android:textColor="#aaaaaa"
-                android:textStyle="bold"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:gravity="center_vertical"
-                android:paddingLeft="8dp"
-                android:paddingRight="20dp"
-                android:textAllCaps="true" />
-        </LinearLayout>
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 9f50780..066b256 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Probeer weer oor <xliff:g id="COUNT">%d</xliff:g> sekondes"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Probeer later weer"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Sleep van bo af na onder om volskerm te verlaat"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Sleep van bo af na onder om volskerm te verlaat."</string>
     <string name="done_label" msgid="2093726099505892398">"Klaar"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Ure se sirkelglyer"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Minute se sirkelglyer"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 029109b..1cd03b1 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -440,7 +440,7 @@
     <string name="permlab_readContacts" msgid="8348481131899886131">"እውቂያዎችህን አንብብ"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"መተግበሪያው በጡባዊ ተኮህ ስለተከማቹ ዕውቂያዎች ያሉትን ውሂቦች በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ ግለሰቦች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያነብ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብህን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል አዘል መተግበሪያዎች የእውቂያህን ውሂብ ሳታውቀው ሊያጋሩት ይችላሉ።"</string>
     <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"መተግበሪያው በስልክህ ስለተከማቹ ዕውቂያዎች ያሉትን ውሂቦች በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ ግለሰቦች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያነብ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብህን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል አዘል መተግበሪያዎች የእውቂያህን ውሂብ ሳታውቀው ሊያጋሩት ይችላሉ።"</string>
-    <string name="permlab_writeContacts" msgid="5107492086416793544">"ዕውቂያዎችህን አስተካክል"</string>
+    <string name="permlab_writeContacts" msgid="5107492086416793544">"ዕውቂያዎችዎን ያስተካክሉ"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"መተግበሪያው በጡባዊ ቱኮህ ስለተከማቹ የዕውቂያዎችህ ውሂብ በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ እውቂያዎች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያስተካክል ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብ እንዲሰርዙ ይፈቅድላቸዋል።"</string>
     <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"መተግበሪያው በስልክህ ስለተከማቹ የዕውቂያዎችህ ውሂብ በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ እውቂያዎች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያስተካክል ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብ እንዲሰርዙ ይፈቅድላቸዋል።"</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"የጥሪ ምዝግብ ማስታወሻን አንብብ"</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"በ<xliff:g id="COUNT">%d</xliff:g> ሰከንዶች ውስጥ እንደገና ይሞክሩ"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"ቆይተው እንደገና ይሞክሩ"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"ከሙሉ ገጽ ማያ ለመውጣት ከላይ ወደታች ጣትዎን ያንቀሳቅሱ"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"ከሙሉ ገጽ ማያ ለመውጣት ጣትዎን ከላይ ወደታች ያንሸራትቱ።"</string>
     <string name="done_label" msgid="2093726099505892398">"ተከናውኗል"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"የሰዓታት ክብ ተንሸራታች"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"የደቂቃዎች ክብ ተንሸራታች"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 07df558..6567206 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"أعد المحاولة خلال <xliff:g id="COUNT">%d</xliff:g> ثانية"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"أعد المحاولة لاحقًا"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"التمرير من أعلى لأسفل للخروج من وضع ملء الشاشة"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"مرر بسرعة من أعلى لأسفل للخروج من وضع ملء الشاشة."</string>
     <string name="done_label" msgid="2093726099505892398">"تم"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"شريط التمرير الدائري للساعات"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"شريط التمرير الدائري للدقائق"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 11e48c9..a3ed201 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1799,7 +1799,8 @@
     <!-- no translation found for restr_pin_countdown:other (4730868920742952817) -->
     <!-- no translation found for restr_pin_try_later (973144472490532377) -->
     <skip />
-    <!-- no translation found for transient_navigation_confirmation (8554991488096662508) -->
+    <!-- no translation found for immersive_mode_confirmation (7227416894979047467) -->
+    <!-- no translation found for immersive_mode_confirmation (8554991488096662508) -->
     <skip />
     <!-- no translation found for done_label (2093726099505892398) -->
     <skip />
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 14091ee..92ab06c 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Опитайте отново след <xliff:g id="COUNT">%d</xliff:g> секунди"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Опитайте отново по-късно"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"За изх. от цял екр. прeк. пръст отгоре надолу"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"За изход от цял екран прекарайте пръст отгоре надолу."</string>
     <string name="done_label" msgid="2093726099505892398">"Готово"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Кръгов плъзгач за часовете"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Кръгов плъзгач за минутите"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index bcfc5a6..0edabd8 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -440,7 +440,7 @@
     <string name="permlab_readContacts" msgid="8348481131899886131">"lectura dels contactes"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Permet que l\'aplicació llegeixi dades sobre els contactes que tinguis emmagatzemats a la tauleta, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb persones concretes. Aquest permís permet que les aplicacions desin les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
     <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Permet que l\'aplicació llegeixi dades sobre els contactes que tinguis emmagatzemats al telèfon, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb persones concretes. Aquest permís permet que les aplicacions desin les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
-    <string name="permlab_writeContacts" msgid="5107492086416793544">"modificació dels contactes"</string>
+    <string name="permlab_writeContacts" msgid="5107492086416793544">"modificar els teus contactes"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Permet que l\'aplicació modifiqui les dades sobre contactes emmagatzemades a la tauleta, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb contactes concrets. Aquest permís permet que les aplicacions suprimeixin dades de contactes."</string>
     <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Permet que l\'aplicació modifiqui les dades sobre contactes emmagatzemades al telèfon, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb contactes concrets. Aquest permís permet que les aplicacions suprimeixin dades de contactes."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"lectura del registre de trucades"</string>
@@ -478,7 +478,7 @@
     <string name="permlab_readFrameBuffer" msgid="6690504248178498136">"llegir la memòria intermèdia de marcs"</string>
     <string name="permdesc_readFrameBuffer" msgid="4937405521809454680">"Permet que l\'aplicació llegeixi el contingut de la memòria intermèdia de marcs."</string>
     <string name="permlab_accessInputFlinger" msgid="5348635270689553857">"accedeix a InputFlinger"</string>
-    <string name="permdesc_accessInputFlinger" msgid="2104864941201226616">"Permet que l\'aplicació utilitzi funcions InputFlinger de baix nivell."</string>
+    <string name="permdesc_accessInputFlinger" msgid="2104864941201226616">"Permet que l\'aplicació utilitzi funcions de baix nivell d\'InputFlinger."</string>
     <string name="permlab_configureWifiDisplay" msgid="5595661694746742168">"configuració de les pantalles Wi-Fi"</string>
     <string name="permdesc_configureWifiDisplay" msgid="7916815158690218065">"Permet a l\'aplicació configurar-se i connectar-se a les pantalles Wi-Fi."</string>
     <string name="permlab_controlWifiDisplay" msgid="393641276723695496">"control de les pantalles Wi-Fi"</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Torna-ho a provar d\'aquí a <xliff:g id="COUNT">%d</xliff:g> segons"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Torna-ho a provar més tard"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Fes llis. dit avall per sortir de pant. comp."</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Fes lliscar el dit cap avall per sortir de la pantalla completa."</string>
     <string name="done_label" msgid="2093726099505892398">"Fet"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Control circular de les hores"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Control circular dels minuts"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 9f7d89a..d8d8479 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Zkuste to znovu za <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Zkuste to znovu později"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Režim celé obrazovky ukončíte přejetím dolů"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Režim celé obrazovky ukončíte přejetím dolů."</string>
     <string name="done_label" msgid="2093726099505892398">"Hotovo"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kruhový posuvník hodin"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kruhový posuvník minut"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 052b7a8..52baa42 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Prøv igen om <xliff:g id="COUNT">%d</xliff:g> sekunder"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Prøv igen senere"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Stryg ned fra toppen for at stoppe fuld skærm"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Stryg ned fra toppen for at afslutte fuld skærm"</string>
     <string name="done_label" msgid="2093726099505892398">"Udfør"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Cirkulær timevælger"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Cirkulær minutvælger"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 8aceb21..16c111c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"In <xliff:g id="COUNT">%d</xliff:g> Sek. wiederholen"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Später erneut versuchen"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Zum Schließen des Vollbilds von oben nach unten wischen"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Zum Schließen des Vollbilds von oben nach unten wischen"</string>
     <string name="done_label" msgid="2093726099505892398">"Fertig"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kreisförmiger Schieberegler für Stunden"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kreisförmiger Schieberegler für Minuten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index eedfbdb..3ece857 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Επανάληψη σε <xliff:g id="COUNT">%d</xliff:g> δευτ."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Δοκιμάστε ξανά αργότερα"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Σάρωση προς τα κάτω για έξοδο από πλήρη οθόνη"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Σάρωση προς τα κάτω για έξοδο από πλήρη οθόνη"</string>
     <string name="done_label" msgid="2093726099505892398">"Τέλος"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Κυκλικό ρυθμιστικό ωρών"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Κυκλικό ρυθμιστικό λεπτών"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index b6e34a6..9fb7998 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Try again in <xliff:g id="COUNT">%d</xliff:g> seconds"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Try again later"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Swipe down from the top to exit full screen"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Swipe down from the top to exit full screen."</string>
     <string name="done_label" msgid="2093726099505892398">"Finished"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index b6e34a6..9fb7998 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Try again in <xliff:g id="COUNT">%d</xliff:g> seconds"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Try again later"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Swipe down from the top to exit full screen"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Swipe down from the top to exit full screen."</string>
     <string name="done_label" msgid="2093726099505892398">"Finished"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Minutes circular slider"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 35ffaca..74ed1c9 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Intentar en <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Vuelve a intentar más tarde."</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Salir de pantalla completa: deslizar abajo"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Desliza el dedo hacia abajo para salir de la pantalla completa."</string>
     <string name="done_label" msgid="2093726099505892398">"Listo"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Control deslizante circular de horas"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 6b42082..e28b8db 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Inténtalo en <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Volver a intentar más tarde"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Desliza hacia abajo para salir de la pantalla completa"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Desliza el dedo hacia abajo para salir de la pantalla completa"</string>
     <string name="done_label" msgid="2093726099505892398">"Listo"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Control deslizante circular de horas"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 433018b..37dcbe4 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Proovige uuesti <xliff:g id="COUNT">%d</xliff:g> sekundi pärast"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Proovige hiljem uuesti"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Täisekraani sulgemiseks pühkige ülevalt alla"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Täisekraanilt väljumiseks pühkige ülevalt alla."</string>
     <string name="done_label" msgid="2093726099505892398">"Valmis"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Ringikujuline tunniliugur"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Ringikujuline minutiliugur"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 9159b2e..7613ea3 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"امتحان پس از <xliff:g id="COUNT">%d</xliff:g> ثانیه"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"بعداً دوباره امتحان کنید"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"برای خروج از تمام صفحه از بالا به پایین بکشید"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"برای خروج از حالت تمام صفحه، انگشت خود را به تندی از بالای صفحه به پایین بکشید."</string>
     <string name="done_label" msgid="2093726099505892398">"انجام شد"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"لغزنده دایره‌ای ساعت"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"لغزنده دایره‌ای دقیقه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8b0b91c..a10b50e 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Yritä uud. <xliff:g id="COUNT">%d</xliff:g> s kul."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Yritä myöhemmin uudelleen"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Poistu koko näytön tilasta liu\'uttamalla alas"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Poistu koko näytön tilasta liu\'uttamalla alas."</string>
     <string name="done_label" msgid="2093726099505892398">"Valmis"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Tuntien ympyränmuotoinen liukusäädin"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Minuuttien ympyränmuotoinen liukusäädin"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 03a312d..6465af3 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Réessayer dans <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Réessayez plus tard"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Balayez vers le bas pour quitter plein écran"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Balayez vers le bas pour quitter le mode plein écran"</string>
     <string name="done_label" msgid="2093726099505892398">"Terminé"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Curseur circulaire des heures"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 07a3890..9b5fed2 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Réessayer dans <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Veuillez réessayer ultérieurement."</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Balayer vers le bas pour quitter le plein écran"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Faites glisser le doigt vers le bas pour quitter le mode plein écran."</string>
     <string name="done_label" msgid="2093726099505892398">"OK"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Curseur circulaire des heures"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index c5ab209..f7ad266 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -205,8 +205,8 @@
     <string name="permgroupdesc_camera" msgid="2933667372289567714">"चित्र या वीडियो कैप्‍चर के लिए कैमरे पर सीधी पहुंच."</string>
     <string name="permgrouplab_screenlock" msgid="8275500173330718168">"स्‍क्रीन लॉक करें"</string>
     <string name="permgroupdesc_screenlock" msgid="7067497128925499401">"आपके उपकरण की लॉक स्क्रीन का व्यवहार प्रभावित करने की क्षमता."</string>
-    <string name="permgrouplab_appInfo" msgid="8028789762634147725">"आपके एप्‍स की जानकारी"</string>
-    <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"अपने उपकरण पर अन्‍य एप्‍स के व्‍यवहार को प्रभावित करने की क्षमता."</string>
+    <string name="permgrouplab_appInfo" msgid="8028789762634147725">"आपके ऐप्स की जानकारी"</string>
+    <string name="permgroupdesc_appInfo" msgid="3950378538049625907">"अपने उपकरण पर अन्‍य ऐप्स के व्‍यवहार को प्रभावित करने की क्षमता."</string>
     <string name="permgrouplab_wallpaper" msgid="3850280158041175998">"वॉलपेपर"</string>
     <string name="permgroupdesc_wallpaper" msgid="5630417854750540154">"उपकरण की वॉलपेपर सेटिंग बदलें."</string>
     <string name="permgrouplab_systemClock" msgid="406535759236612992">"घड़ी"</string>
@@ -225,8 +225,8 @@
     <string name="permgroupdesc_systemTools" msgid="8162102602190734305">"सिस्‍टम का निम्‍न-स्‍तर पहुंच और नियंत्रण."</string>
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"डेवलपमेंट टूल"</string>
     <string name="permgroupdesc_developmentTools" msgid="7058828032358142018">"सुविधाएं जो केवल एप्स डेवलपर के लिए आवश्यक हैं."</string>
-    <string name="permgrouplab_display" msgid="4279909676036402636">"अन्‍य एप्‍स UI"</string>
-    <string name="permgroupdesc_display" msgid="6051002031933013714">"अन्‍य एप्‍स के UI को प्रभावित करें."</string>
+    <string name="permgrouplab_display" msgid="4279909676036402636">"अन्‍य ऐप्स UI"</string>
+    <string name="permgroupdesc_display" msgid="6051002031933013714">"अन्‍य ऐप्स के UI को प्रभावित करें."</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>
@@ -243,7 +243,7 @@
     <string name="permlab_statusBar" msgid="7417192629601890791">"स्‍थिति बार अक्षम या बदलें"</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="permlab_install_shortcut" msgid="4279070216371564234">"शॉर्टकट इंस्‍टॉल करें"</string>
@@ -251,7 +251,7 @@
     <string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"शॉर्टकट अनइंस्टॉल करें"</string>
     <string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"एप्‍लिकेशन को उपयोगकर्ता के हस्‍तक्षेप के बिना होमस्‍क्रीन शॉर्टकट निकालने की अनुमति देता है."</string>
     <string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"आउटगोइंग कॉल को कहीं और भेजें"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"एप्‍स को आउटगोइंग कॉल संसाधित करने और डायल किए जाने वाला नंबर बदलने देता है. यह अनुमति एप्‍स को आउटगोइंग कॉल की निगरानी करने, रीडायरेक्‍ट करने, या उन्‍हें रोकने देती है."</string>
+    <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"ऐप्स को आउटगोइंग कॉल संसाधित करने और डायल किए जाने वाला नंबर बदलने देता है. यह अनुमति ऐप्स को आउटगोइंग कॉल की निगरानी करने, रीडायरेक्‍ट करने, या उन्‍हें रोकने देती है."</string>
     <string name="permlab_receiveSms" msgid="8673471768947895082">"पाठ संदेश (SMS) प्राप्त करें"</string>
     <string name="permdesc_receiveSms" msgid="6424387754228766939">"एप्स को SMS संदेशों को प्राप्‍त और संसाधित करने देता है. इसका अर्थ है कि एप्स आपके उपकरण पर भेजे गए संदेशों की निगरानी आपको दिखाए बिना कर सकता है और उन्‍हें हटा सकता है."</string>
     <string name="permlab_receiveMms" msgid="1821317344668257098">"पाठ संदेश (MMS) प्राप्त करें"</string>
@@ -259,7 +259,7 @@
     <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"आपातकालीन प्रसारण प्राप्त करें"</string>
     <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"एप्स को आपातकालीन प्रसारण संदेशों को प्राप्त करने और संसाधित करने देता है. यह अनुमति केवल सिस्टम एप्स में उपलब्ध है."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"सेल प्रसारण संदेश पढ़ें"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"एप्‍स को आपके उपकरण द्वारा प्राप्त सेल प्रसारण संदेशों को पढ़ने देता है. कुछ स्‍थानों पर आपको आपातकालीन स्‍थितियों की चेतावनी देने के लिए सेल प्रसारण अलर्ट वितरित किए जाते हैं. आपातकालीन सेल प्रसारण प्राप्त होने पर दुर्भावनापूर्ण एप्‍स आपके उपकरण के निष्‍पादन या संचालन में हस्‍तक्षेप कर सकते हैं."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"ऐप्स को आपके उपकरण द्वारा प्राप्त सेल प्रसारण संदेशों को पढ़ने देता है. कुछ स्‍थानों पर आपको आपातकालीन स्‍थितियों की चेतावनी देने के लिए सेल प्रसारण अलर्ट वितरित किए जाते हैं. आपातकालीन सेल प्रसारण प्राप्त होने पर दुर्भावनापूर्ण ऐप्स आपके उपकरण के निष्‍पादन या संचालन में हस्‍तक्षेप कर सकते हैं."</string>
     <string name="permlab_sendSms" msgid="5600830612147671529">"SMS संदेश भेजें"</string>
     <string name="permdesc_sendSms" msgid="7094729298204937667">"एप्स को SMS संदेशों को भेजने देता है. इसके परिणामस्वरूप अप्रत्‍याशित शुल्‍क लागू हो सकते हैं. दुर्भावनापूर्ण एप्स आपकी पुष्टि के बिना संदेश भेजकर आपका धन व्‍यय कर सकते हैं."</string>
     <string name="permlab_sendRespondViaMessageRequest" msgid="8713889105305943200">"संदेश-द्वारा-जवाब भेजें ईवेंट"</string>
@@ -272,7 +272,7 @@
     <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"एप्स को आपके फ़ोन या सिम कार्ड में संग्रहीत SMS संदेशों को लिखने देता है.  दुर्भावनापूर्ण एप्स आपके संदेशों को हटा सकते हैं."</string>
     <string name="permlab_receiveWapPush" msgid="5991398711936590410">"पाठ संदेश (WAP) प्राप्त करें"</string>
     <string name="permdesc_receiveWapPush" msgid="748232190220583385">"एप्स को WAP संदेशों को प्राप्‍त और संसाधित करने देता है. इस अनुमति में आपको भेजे गए संदेशों की निगरानी आपको दिखाए बिना करने और हटाने की क्षमता शामिल है."</string>
-    <string name="permlab_getTasks" msgid="6466095396623933906">"चल रहे एप्‍स पुनर्प्राप्त करें"</string>
+    <string name="permlab_getTasks" msgid="6466095396623933906">"चल रहे ऐप्स पुनर्प्राप्त करें"</string>
     <string name="permdesc_getTasks" msgid="7454215995847658102">"एप्स को वर्तमान में और हाल ही में चल रहे कार्यों के बारे में जानकारी को पुन: प्राप्‍त करने देता है. इससे एप्स उपकरण पर उपयोग किए गए एप्स के बारे में जानकारी खोज सकता है."</string>
     <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"उपयोगकर्ताओं के बीच सहभागिता करें"</string>
     <string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"एप्स को उपकरण पर भिन्न उपयोगकर्ताओं के बीच कार्य निष्पादित करने देता है. दुर्भावनापूर्ण एप्स उपयोगकर्ताओं के बीच सुरक्षा का उल्लंघन करने के लिए इसका उपयोग कर सकते हैं."</string>
@@ -280,49 +280,49 @@
     <string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"उपयोगकर्ताओं के बीच सभी संभव सहभागिता करने देता है."</string>
     <string name="permlab_manageUsers" msgid="1676150911672282428">"उपयोगकर्ता प्रबंधित करें"</string>
     <string name="permdesc_manageUsers" msgid="8409306667645355638">"एप्स को उपकरण पर क्वेरी, निर्माण और हटाने सहित उपयोगकर्ताओं को प्रबंधित करने की सुविधा देता है."</string>
-    <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"चल रहे एप्‍स के विवरण प्राप्त करें"</string>
+    <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"चल रहे ऐप्स के विवरण प्राप्त करें"</string>
     <string name="permdesc_getDetailedTasks" msgid="153824741440717599">"एप्स को वर्तमान में और हाल ही में चल रहे कार्यों की जानकारी प्राप्त करने देता है. दुर्भावनापूर्ण एप्स अन्य एप्स के बारे में निजी जानकारी खोज सकते हैं."</string>
-    <string name="permlab_reorderTasks" msgid="2018575526934422779">"चल रहे एप्‍स पुन: क्रमित करें"</string>
+    <string name="permlab_reorderTasks" msgid="2018575526934422779">"चल रहे ऐप्स पुन: क्रमित करें"</string>
     <string name="permdesc_reorderTasks" msgid="7734217754877439351">"एप्स को कार्यों को अग्रभूमि और पृष्‍ठभूमि पर ले जाने देता है. एप्स आपके इनपुट के बिना यह कर सकता है."</string>
     <string name="permlab_removeTasks" msgid="6821513401870377403">"चलने वाले एप्स रोकें"</string>
-    <string name="permdesc_removeTasks" msgid="1394714352062635493">"किसी एप्‍स को कार्यों को निकालने और उनके एप्‍स समाप्त करने देता है. दुर्भावनापूर्ण एप्‍स अन्‍य एप्‍स का व्‍यवहार बाधित कर सकते हैं."</string>
+    <string name="permdesc_removeTasks" msgid="1394714352062635493">"किसी ऐप्स को कार्यों को निकालने और उनके ऐप्स समाप्त करने देता है. दुर्भावनापूर्ण ऐप्स अन्‍य ऐप्स का व्‍यवहार बाधित कर सकते हैं."</string>
     <string name="permlab_manageActivityStacks" msgid="7391191384027303065">"गतिविधि स्टैक प्रबंधित करें"</string>
     <string name="permdesc_manageActivityStacks" msgid="1615881933034084440">"एप्स को ऐसे गतिविधि स्टैक जोड़ने, निकालने, और बदलने देता है जिनमें अन्य एप्स चलते हों. दुर्भावनापूर्ण एप्स अन्य एप्स के व्यवहार में बाधा डाल सकते हैं."</string>
     <string name="permlab_startAnyActivity" msgid="2918768238045206456">"कोई गतिविधि प्रारंभ करें"</string>
-    <string name="permdesc_startAnyActivity" msgid="997823695343584001">"अनुमति सुरक्षा या निर्यात की स्‍थिति पर ध्‍यान दिए बिना, एप्‍स को कोई गतिविधि प्रारंभ करने देता है."</string>
+    <string name="permdesc_startAnyActivity" msgid="997823695343584001">"अनुमति सुरक्षा या निर्यात की स्‍थिति पर ध्‍यान दिए बिना, ऐप्स को कोई गतिविधि प्रारंभ करने देता है."</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_setScreenCompatibility" msgid="692043618693917374">"ऐप्स को अन्‍य ऐप्स के स्‍क्रीन संगतता मोड को नियंत्रित करने देता है. दुर्भावनापूर्ण ऐप्स अन्‍य ऐप्स का व्‍यवहार बाधित कर सकते हैं."</string>
+    <string name="permlab_setDebugApp" msgid="3022107198686584052">"ऐप्स डीबग करना सक्षम करें"</string>
     <string name="permdesc_setDebugApp" msgid="4474512416299013256">"एप्स को अन्य एप्स के लिए डीबग किया जाना चालू करने देता है. दुर्भावनापूर्ण एप्स इसका उपयोग अन्य एप्स को समाप्त करने के लिए कर सकते हैं."</string>
     <string name="permlab_changeConfiguration" msgid="4162092185124234480">"सिस्‍टम प्रदर्शन सेटिंग बदलें"</string>
     <string name="permdesc_changeConfiguration" msgid="4372223873154296076">"एप्स को वर्तमान कॉन्फ़िगरेशन, जैसे स्थान या समग्र अक्षरों का आकार, बदलने देता है."</string>
     <string name="permlab_enableCarMode" msgid="5684504058192921098">"कार मोड सक्षम करें"</string>
     <string name="permdesc_enableCarMode" msgid="4853187425751419467">"एप्स को कार मोड सक्षम करने देता है."</string>
-    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"अन्‍य एप्‍स बंद करें"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"एप्स को अन्‍य एप्‍स की पृष्ठभूमि प्रक्रियाओं को समाप्त करने देता है. यह अन्य एप्स का चलना रोक सकता है."</string>
-    <string name="permlab_forceStopPackages" msgid="2329627428832067700">"अन्‍य एप्‍स बलपूर्वक बंद करें"</string>
+    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"अन्‍य ऐप्स बंद करें"</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"एप्स को अन्‍य ऐप्स की पृष्ठभूमि प्रक्रियाओं को समाप्त करने देता है. यह अन्य एप्स का चलना रोक सकता है."</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_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_temporary_enable_accessibility" msgid="2312612135127310254">"आसान तरीका को अस्थायी रूप से सक्षम करें"</string>
     <string name="permdesc_temporary_enable_accessibility" msgid="8079456293182975464">"एप्स को उपकरण पर आसान तरीका को अस्थायी रूप से सक्षम करने देता है. दुर्भावनापूर्ण एप्स उपयोगकर्ता की सहमति के बिना आसान तरीका को सक्षम कर सकते हैं."</string>
     <string name="permlab_retrieve_window_info" msgid="8532295199112519378">"विंडो जानकारी प्राप्त करें"</string>
-    <string name="permdesc_retrieve_window_info" msgid="4998836370424186849">"एप्‍स को विंडो प्रबंधक से windows के बारे में जानकारी प्राप्त करने देता है. दुर्भावनापूर्ण एप्‍स आंतरिक सिस्टम उपयोग के लिए अभिप्रेत जानकारी को प्राप्त कर सकते हैं."</string>
+    <string name="permdesc_retrieve_window_info" msgid="4998836370424186849">"ऐप्स को विंडो प्रबंधक से windows के बारे में जानकारी प्राप्त करने देता है. दुर्भावनापूर्ण ऐप्स आंतरिक सिस्टम उपयोग के लिए अभिप्रेत जानकारी को प्राप्त कर सकते हैं."</string>
     <string name="permlab_filter_events" msgid="8675535648807427389">"ईवेंट फ़िल्टर करें"</string>
-    <string name="permdesc_filter_events" msgid="8006236315888347680">"एप्‍स को इनपुट फ़िल्‍टर पंजीकृत करने देता है, जो सभी उपयोगकर्ता ईवेंट के स्‍ट्रीम को भेजे जाने से पहले फ़िल्‍टर करता है. दुर्भावनापूर्ण एप्‍स उपयोगकर्ता के हस्‍तक्षेप के बिना सिस्‍टम UI को नियंत्रित कर सकता है."</string>
+    <string name="permdesc_filter_events" msgid="8006236315888347680">"ऐप्स को इनपुट फ़िल्‍टर पंजीकृत करने देता है, जो सभी उपयोगकर्ता ईवेंट के स्‍ट्रीम को भेजे जाने से पहले फ़िल्‍टर करता है. दुर्भावनापूर्ण ऐप्स उपयोगकर्ता के हस्‍तक्षेप के बिना सिस्‍टम UI को नियंत्रित कर सकता है."</string>
     <string name="permlab_magnify_display" msgid="5973626738170618775">"डिस्प्ले को आवर्धित करें"</string>
     <string name="permdesc_magnify_display" msgid="7121235684515003792">"एप्स को डिस्प्ले की सामग्री आवर्धित करने देता है. दुर्भावनापूर्ण एप्स डिस्प्ले सामग्री को इस तरह से बदल सकते हैं कि उपकरण अनुपयोगी रेंडर होता है."</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_stopAppSwitches" msgid="4138608610717425573">"ऐप्स स्‍विच करने से रोकता है"</string>
+    <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"उपयोगकर्ता को दूसरे ऐप्स पर स्‍विच करने से रोकता है."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"वर्तमान एप्स की जानकारी प्राप्त करें"</string>
     <string name="permdesc_getTopActivityInfo" msgid="2512448855496067131">"धारक को स्क्रीन के अग्रभाग में स्थित वर्तमान एप्स के बारे में निजी जानकारी प्राप्त करने देती है."</string>
-    <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"सभी एप्‍स की लॉन्‍चिंग की निगरानी करें और उसे नियंत्रित करें"</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>
@@ -332,111 +332,111 @@
     <string name="permdesc_broadcastWapPush" msgid="4783402525039442729">"एप्स को वह सूचना प्रसारित करने देता है जो WAP PUSH संदेश को प्राप्त हुआ है. दुर्भावनापूर्ण एप्स इसका उपयोग नकली MMS संदेश प्राप्त करने या किसी वेबपृष्ठ की सामग्री को दुर्भावनापूर्ण दूसरे रूप से चुपचाप प्रतिस्थापित करने के लिए कर सकते हैं."</string>
     <string name="permlab_setProcessLimit" msgid="2451873664363662666">"चल रही प्रक्रियाओं की संख्‍या सीमित करें"</string>
     <string name="permdesc_setProcessLimit" msgid="7318061314040879542">"एप्स को चलाई जाने वाली अधिकतम प्रक्रियाओं को नियंत्रित करने देता है. सामान्य एप्स के लिए कभी आवश्यक नहीं होती."</string>
-    <string name="permlab_setAlwaysFinish" msgid="550958507798796965">"पृष्ठभूमि एप्‍स को बलपूर्वक बंद करें"</string>
+    <string name="permlab_setAlwaysFinish" msgid="550958507798796965">"पृष्ठभूमि ऐप्स को बलपूर्वक बंद करें"</string>
     <string name="permdesc_setAlwaysFinish" msgid="7471310652868841499">"एप्स को यह नियंत्रित करने देता है कि पृष्ठभूमि में जाते ही गतिविधियां पूर्ण हो जाती है या नही. सामान्य एप्स के लिए कभी आवश्यकता नहीं होती."</string>
     <string name="permlab_batteryStats" msgid="2789610673514103364">"बैटरी के आंकड़े पढ़ें"</string>
     <string name="permdesc_batteryStats" msgid="5897346582882915114">"एप्स को वर्तमान निम्न-स्तरीय बैटरी उपयोग डेटा पढ़ने देती है. एप्स को आपके द्वारा उपयोग किए जाने वाले एप्स के बारे में विस्तृत जानकारी ढूंढने दे सकती है."</string>
     <string name="permlab_updateBatteryStats" msgid="3719689764536379557">"बैटरी के आंकड़े संशोधित करें"</string>
-    <string name="permdesc_updateBatteryStats" msgid="6862817857178025002">"एप्‍स को बैटरी के संकलित आंकड़ों को संशोधित करने देती है. सामान्‍य एप्‍स के द्वारा उपयोग करने के लिए नहीं."</string>
+    <string name="permdesc_updateBatteryStats" msgid="6862817857178025002">"ऐप्स को बैटरी के संकलित आंकड़ों को संशोधित करने देती है. सामान्‍य ऐप्स के द्वारा उपयोग करने के लिए नहीं."</string>
     <string name="permlab_getAppOpsStats" msgid="1508779687436585744">"एप्स संचालन आंकड़े प्राप्त करें"</string>
     <string name="permdesc_getAppOpsStats" msgid="6243887041577912877">"एप्स को संकलित एप्स संचालन आंकड़े प्राप्त करने देता है. सामान्य एप्स के द्वारा उपयोग के लिए नहीं."</string>
     <string name="permlab_updateAppOpsStats" msgid="8829097373851521505">"एप्स कार्यवाही के आंकड़े बदलें"</string>
     <string name="permdesc_updateAppOpsStats" msgid="50784596594403483">"एप्स को एप्स कार्यवाही के एकत्रित आंकड़े बदलने देता है. सामान्य एप्स के द्वारा उपयोग करने के लिए नहीं."</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">"एप्‍स को पूर्ण बैकअप पुष्टिकरण UI लॉन्‍च करने देता है. किसी एप्‍स द्वारा उपयोग के लिए नहीं."</string>
+    <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"ऐप्स को पूर्ण बैकअप पुष्टिकरण UI लॉन्‍च करने देता है. किसी ऐप्स द्वारा उपयोग के लिए नहीं."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"अनधिकृत विंडो दिखाएं"</string>
-    <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"किसी एप्‍स को ऐसी विंडो बनाने देता है जिनका उपयोग आंतरिक सिस्‍टम उपयोगकर्ता इंटरफ़ेस द्वारा किया जाना है. सामान्‍य एप्‍स द्वारा उपयोग करने के लिए नहीं."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"अन्‍य एप्‍स पर खींचें"</string>
+    <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"किसी ऐप्स को ऐसी विंडो बनाने देता है जिनका उपयोग आंतरिक सिस्‍टम उपयोगकर्ता इंटरफ़ेस द्वारा किया जाना है. सामान्‍य ऐप्स द्वारा उपयोग करने के लिए नहीं."</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"अन्‍य ऐप्स पर खींचें"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"एप्स को अन्य एप्स के शीर्ष पर या उपयोगकर्ता इंटरफ़ेस के हिस्सों पर आने देती है. वे किसी भी एप्स में इंटरफ़ेस के आपके उपयोग में हस्तक्षेप कर सकते हैं, या उस चीज को बदल सकती है जिसके बारे में आपको लगता है कि आप उसे अन्य एप्स में देख रहे हैं."</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_freezeScreen" msgid="4708181184441880175">"स्क्रीन को स्थिर करें"</string>
     <string name="permdesc_freezeScreen" msgid="8558923789222670064">"पूर्ण-स्क्रीन संक्रमण के लिए एप्स को अस्थायी रूप से स्क्रीन को स्थिर करने देता है."</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_bindAccessibilityService" msgid="5357733942556031593">"आसान तरीका सेवा से आबद्ध करें"</string>
-    <string name="permdesc_bindAccessibilityService" msgid="7034615928609331368">"धारक को किसी आसान तरीका सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य एप्‍स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
+    <string name="permdesc_bindAccessibilityService" msgid="7034615928609331368">"धारक को किसी आसान तरीका सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_bindPrintService" msgid="8462815179572748761">"प्रिंट सेवा से आबद्ध करें"</string>
-    <string name="permdesc_bindPrintService" msgid="7960067623209111135">"धारक को किसी प्रिंट सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य एप्‍स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
+    <string name="permdesc_bindPrintService" msgid="7960067623209111135">"धारक को किसी प्रिंट सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_bindPrintSpoolerService" msgid="6807762783744125954">"प्रिंट स्पूलर सेवा से आबद्ध करें"</string>
-    <string name="permdesc_bindPrintSpoolerService" msgid="3680552285933318372">"धारक को प्रिंट स्पूलर सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य एप्‍स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
+    <string name="permdesc_bindPrintSpoolerService" msgid="3680552285933318372">"धारक को प्रिंट स्पूलर सेवा के शीर्ष-स्‍तर इंटरफ़ेस से आबद्ध होने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_bindNfcService" msgid="2752731300419410724">"NFC सेवा से आबद्ध रहें"</string>
     <string name="permdesc_bindNfcService" msgid="6120647629174066862">"धारक को ऐसे एप्स से आबद्ध रहने देता है जो NFC कार्ड का अनुकरण कर रहे हैं. सामान्य एप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</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_manageDeviceAdmins" msgid="4248828900045808722">"उपकरण उपकरण सुचारू ढ़ंग से चलाने वाले को जोड़ें या निकालें"</string>
     <string name="permdesc_manageDeviceAdmins" msgid="5025608167709942485">"धारक को सक्रिय डिवाइस व्यवस्थापकों को जोड़ने या निकालने देता है. सामान्य एप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</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="permdesc_setPointerSpeed" msgid="6866563234274104233">"एप्स को माउस या ट्रैकपैड सूचक गति को किसी भी समय बदलने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"कीबोर्ड लेआउट बदलें"</string>
-    <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"एप्‍स को कीबोर्ड लेआउट बदलने देता है. सामान्‍य एप्‍स के लिए कभी आवश्‍यक नहीं है."</string>
+    <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"ऐप्स को कीबोर्ड लेआउट बदलने देता है. सामान्‍य ऐप्स के लिए कभी आवश्‍यक नहीं है."</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" product="tablet" msgid="8525189272329086137">"एप्‍स को स्मृति में स्‍वयं के कुछ हिस्सों को लगातार बनाने की अनुमति देता है. यह अन्‍य एप्स के लिए उपलब्‍ध स्‍मृति को सीमित कर टेबलेट को धीमा कर सकता है."</string>
-    <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"एप्‍स को स्मृति में स्‍वयं के कुछ हिस्सों को लगातार बनाने देता है. यह अन्‍य एप्स के लिए उपलब्‍ध स्‍मृति को सीमित कर फ़ोन को धीमा कर सकता है."</string>
-    <string name="permlab_deletePackages" msgid="184385129537705938">"एप्‍स हटाएं"</string>
+    <string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"ऐप्स को यह अनुरोध करने देता है कि दिया गया सिग्नल सभी जारी प्रक्रियाओं को भेजा जाए."</string>
+    <string name="permlab_persistentActivity" msgid="8841113627955563938">"ऐप्स को हमेशा चलने वाला बनाएं"</string>
+    <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"ऐप्स को स्मृति में स्‍वयं के कुछ हिस्सों को लगातार बनाने की अनुमति देता है. यह अन्‍य एप्स के लिए उपलब्‍ध स्‍मृति को सीमित कर टेबलेट को धीमा कर सकता है."</string>
+    <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"ऐप्स को स्मृति में स्‍वयं के कुछ हिस्सों को लगातार बनाने देता है. यह अन्‍य एप्स के लिए उपलब्‍ध स्‍मृति को सीमित कर फ़ोन को धीमा कर सकता है."</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="permlab_clearAppUserData" msgid="274109191845842756">"अन्‍य ऐप्स का डेटा हटाएं"</string>
     <string name="permdesc_clearAppUserData" msgid="4625323684125459488">"एप्स को उपयोगकर्ता डेटा साफ़ करने देता है."</string>
-    <string name="permlab_deleteCacheFiles" msgid="3128665571837408675">"अन्‍य एप्‍स के संचय हटाएं"</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="permlab_installPackages" msgid="2199128482820306924">"सीधे ऐप्स इंस्‍टॉल करें"</string>
     <string name="permdesc_installPackages" msgid="5628530972548071284">"एप को नए या नई जानकारी वाले Android पैकेज इंस्टॉल करने देता है. दुर्भावनापूर्ण एप्स इसका उपयोग अनियंत्रित रूप से सशक्त अनुमतियों वाले नए एप्स जोड़ने में कर सकते हैं."</string>
     <string name="permlab_clearAppCache" msgid="7487279391723526815">"सभी एप्स संचय डेटा हटाएं"</string>
     <string name="permdesc_clearAppCache" product="tablet" msgid="8974640871945434565">"एप्स को अन्य एप्स की संचय निर्देशिकाओं में से फ़ाइलें हटाकर टेबलेट संग्रहण को खाली करने देती है. इससे अन्य एप्स अधिक धीमे प्रारंभ हो सकते हैं क्योंकि उन्हें अपना डेटा पुनर्प्राप्त करने की आवश्यकता होती है."</string>
     <string name="permdesc_clearAppCache" product="default" msgid="2459441021956436779">"एप्स को अन्य एप्स की संचय निर्देशिकाओं में से फ़ाइलें हटाकर फ़ोन संग्रहण को खाली करने देती है. इससे अन्य एप्स अधिक धीमे प्रारंभ हो सकते हैं क्योंकि उन्हें अपना डेटा पुनर्प्राप्त करने की आवश्यकता होती है."</string>
-    <string name="permlab_movePackage" msgid="3289890271645921411">"एप्‍स संसाधनों को ले जाएं"</string>
-    <string name="permdesc_movePackage" msgid="319562217778244524">"एप्‍स को एप्‍स संसाधनों को आंतरिक से बाहरी मीडिया में और इसके विपरीत ले जाने देता है."</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="permlab_manageCaCertificates" msgid="1678391896786882014">"विश्वसनीय क्रेडेंशियल प्रबंधित करें"</string>
     <string name="permdesc_manageCaCertificates" msgid="4015644047196937014">"एप्स को CA प्रमाणपत्रों को विश्वसनीय क्रेडेंशियल के रूप में इंस्टॉल और अनइंस्टॉल करने दें"</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"निदान के स्‍वामित्‍व वाले संसाधनों को पढ़ें/लिखें"</string>
-    <string name="permdesc_diagnostic" msgid="6608295692002452283">"एप्‍स को diag समूह के स्‍वामित्‍व वाले किसी संसाधन को पढ़ने और उसमें लिखने देता है; उदाहरण के लिए, /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">"ऐप्स को diag समूह के स्‍वामित्‍व वाले किसी संसाधन को पढ़ने और उसमें लिखने देता है; उदाहरण के लिए, /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="permlab_setPreferredApplications" msgid="8463181628695396391">"पसंदीदा ऐप्स सेट करें"</string>
     <string name="permdesc_setPreferredApplications" msgid="4973986762241783712">"एप्स को आपके पसंदीदा एप्स को संशोधित करने देता है. दुर्भावनापूर्ण एप्स आपसे निजी डेटा एकत्रित करने के लिए आपके मौजूदा एप्स को स्पूफ़ करके, चलाए जाने वाले एप्स को चुपचाप बदल सकते हैं."</string>
     <string name="permlab_writeSettings" msgid="2226195290955224730">"सिस्‍टम सेटिंग बदलें"</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="5312965565987800025">"प्रारंभ होने पर चलाएं"</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="7749760494399915651">"एप्‍स को स्‍टिकी प्रसारण भेजने देता है, जो प्रसारण समाप्त होने के बाद भी बने रहते हैं. अत्यधिक उपयोग, टेबलेट की बहुत अधिक स्मृति का उपयोग करके उसे धीमा या अस्‍थिर कर सकता है."</string>
-    <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"एप्‍स को स्‍टिकी प्रसारण भेजने देता है, जो प्रसारण समाप्त होने के बाद भी बने रहते हैं. अत्यधिक उपयोग, फ़ोन की बहुत अधिक स्मृति का उपयोग करके उसे धीमा या अस्‍थिर कर सकता है."</string>
+    <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"ऐप्स को स्‍टिकी प्रसारण भेजने देता है, जो प्रसारण समाप्त होने के बाद भी बने रहते हैं. अत्यधिक उपयोग, टेबलेट की बहुत अधिक स्मृति का उपयोग करके उसे धीमा या अस्‍थिर कर सकता है."</string>
+    <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"ऐप्स को स्‍टिकी प्रसारण भेजने देता है, जो प्रसारण समाप्त होने के बाद भी बने रहते हैं. अत्यधिक उपयोग, फ़ोन की बहुत अधिक स्मृति का उपयोग करके उसे धीमा या अस्‍थिर कर सकता है."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"अपने संपर्क पढ़ें"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"एप्स को आपके टेबलेट में संग्रहीत संपर्कों के डेटा को, साथ ही आपके द्वारा विशिष्ट व्यक्तियों को कॉल करने, ईमेल करने, या अन्‍य तरीके से संवाद करने की आवृत्ति को पढ़ने देता है. यह अनुमति एप्स को आपके संपर्क डेटा को सहेजने देती है, और दुर्भावनापूर्ण एप्स आपकी जानकारी के बिना संपर्क डेटा को साझा कर सकते हैं."</string>
     <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"एप्स को आपके फ़ोन में संग्रहीत संपर्कों के डेटा को, साथ ही आपके द्वारा विशिष्ट व्यक्तियों को कॉल करने, ईमेल करने, या अन्‍य तरीके से संवाद करने की आवृत्ति को पढ़ने देता है. यह अनुमति एप्स को आपके संपर्क डेटा को सहेजने देती है, और दुर्भावनापूर्ण एप्स आपकी जानकारी के बिना संपर्क डेटा को साझा कर सकते हैं."</string>
@@ -447,8 +447,8 @@
     <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"एप्स को आपके फ़ोन का कॉल लॉग पढ़ने देता है, जिसमें इनकमिंग और आउटगोइंग कॉल का डेटा शामिल है. यह अनुमति एप्स को आपके कॉल लॉग डेटा को सहेजने देती है, और दुर्भावनापूर्ण एप्स आपकी जानकारी के बिना कॉल लॉग डेटा को साझा कर सकते हैं."</string>
     <string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"एप्स को आपके फ़ोन का कॉल लॉग पढ़ने देता है, जिसमें इनकमिंग और आउटगोइंग कॉल का डेटा शामिल है. यह अनुमति एप्स को आपके कॉल लॉग डेटा को सहेजने देती है, और दुर्भावनापूर्ण एप्स आपकी जानकारी के बिना कॉल लॉग डेटा को साझा कर सकते हैं."</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="4701889852612716678">"स्‍वयं का संपर्क कार्ड पढ़ें"</string>
     <string name="permdesc_readProfile" product="default" msgid="5462475151849888848">"एप्स को आपके उपकरण में संग्रहीत व्यक्तिगत प्रोफ़ाइल जानकारी, जैसे आपका नाम और संपर्क जानकारी, पढ़ने देता है. इसका अर्थ है कि एप्स आपको पहचान सकता है और आपकी प्रोफ़ाइल जानकारी अन्य लोगों को भेज सकता है."</string>
     <string name="permlab_writeProfile" msgid="907793628777397643">"स्‍वयं का संपर्क कार्ड बदलें"</string>
@@ -474,23 +474,23 @@
     <string name="permlab_accessCoarseLocation" msgid="4887895362354239628">"अनुमानित स्थान (नेटवर्क-आधारित)"</string>
     <string name="permdesc_accessCoarseLocation" msgid="2538200184373302295">"एप्स को आपका अनुमानित स्थान प्राप्त करने देती है. इस स्थान को सेल टॉवर और Wi-Fi जैसे नेटवर्क स्थान स्रोतों का उपयोग करके स्थान सेवाओं द्वारा प्राप्त किया गया है. एप्स द्वारा इन स्थान सेवाओं का उपयोग करने के लिए इन्हें चालू होना चाहिए और आपके उपकरण में उपलब्ध होना चाहिए. एप्स इसका उपयोग यह पता लगाने में कर सकते हैं कि आप लगभग कहां पर हैं."</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_accessInputFlinger" msgid="5348635270689553857">"InputFlinger एक्सेस करें"</string>
-    <string name="permdesc_accessInputFlinger" msgid="2104864941201226616">"एप्‍स को InputFlinger निम्‍न-स्‍तर सुविधाओं का उपयोग करने देता है."</string>
+    <string name="permdesc_accessInputFlinger" msgid="2104864941201226616">"ऐप्स को InputFlinger निम्‍न-स्‍तर सुविधाओं का उपयोग करने देता है."</string>
     <string name="permlab_configureWifiDisplay" msgid="5595661694746742168">"Wifi डिस्प्ले को कॉन्फ़िगर करें"</string>
     <string name="permdesc_configureWifiDisplay" msgid="7916815158690218065">"एप्स को कॉन्फ़िगर करने देता है और Wifi डिस्प्ले से कनेक्ट करता है."</string>
     <string name="permlab_controlWifiDisplay" msgid="393641276723695496">"Wifi डिस्प्ले को नियंत्रित करें"</string>
     <string name="permdesc_controlWifiDisplay" msgid="4543912292681826986">"एप्स को Wifi डिस्प्ले की निम्न-स्तर की सुविधाएं नियंत्रित करने देता है."</string>
     <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"ऑडियो आउटपुट को कैप्‍चर करें"</string>
-    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"एप्‍स को ऑडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
+    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"ऐप्स को ऑडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
     <string name="permlab_captureAudioHotword" msgid="1890553935650349808">"हॉटवर्ड पहचान"</string>
     <string name="permdesc_captureAudioHotword" msgid="9151807958153056810">"एप्लिकेशन को हॉटवर्ड पहचान के लिए ऑडियो कैप्चर करने देती है. कैप्चर पृष्ठभूमि में हो सकता है लेकिन वह अन्य ऑडियो कैप्चर (उदा. कैमकॉर्डर) को नहीं रोकता."</string>
     <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"वीडियो आउटपुट को कैप्‍चर करें"</string>
-    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"एप्‍स को वीडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
+    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"ऐप्स को वीडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
     <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"सुरक्षित वीडियो आउटपुट को कैप्‍चर करें"</string>
-    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"एप्‍स को सुरक्षित वीडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
+    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"ऐप्स को सुरक्षित वीडियो आउटपुट को कैप्‍चर और रीडायरेक्‍ट करने देता है."</string>
     <string name="permlab_mediaContentControl" msgid="8749790560720562511">"मीडिया प्लेबैक और मेटाडेटा एक्सेस नियंत्रित करें"</string>
     <string name="permdesc_mediaContentControl" msgid="1637478200272062">"एप्लिकेशन को मीडिया प्लेबैक नियंत्रित करने देती है और मीडिया जानकारी (शीर्षक, लेखक...) एक्सेस करने देती है."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"अपनी ऑडियो सेटिंग बदलें"</string>
@@ -503,53 +503,53 @@
     <string name="permdesc_cameraDisableTransmitLed" msgid="4764585465480295341">"पहले से इंस्टॉल किए गए सिस्टम एप्स को कैमरे को संकेतक LED का उपयोग करने से अक्षम करती है."</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" product="nosdcard" msgid="2927361537942591841">"USB संग्रहण फ़ाइल सिस्‍टम पर पहुंचें"</string>
     <string name="permlab_mount_unmount_filesystems" product="default" msgid="4402305049890953810">"SD कार्ड फ़ाइल सिस्‍टम पर पहुंचें"</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" product="nosdcard" msgid="6227819582624904972">"USB संग्रहण मिटाएं"</string>
     <string name="permlab_mount_format_filesystems" product="default" msgid="262582698639274056">"SD कार्ड मिटाएं"</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="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="7696427026057705834">"कंपन नियंत्रित करें"</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 USB प्रोटोकॉल लागू करने के लिए कर्नेल MTP ड्राइवर में पहुंच की अनुमति देता है."</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="3740797576113760827">"एप्स को आपके हस्‍तक्षेप के बिना फ़ोन नंबर पर कॉल करने देता है. इसके परिणाम अप्रत्‍याशित शुल्‍क या कॉल हो सकते हैं. ध्यान दें कि यह एप्स को आपातकालीन नंबर पर कॉल नहीं करने देता. दुर्भावनापूर्ण एप्स आपकी पुष्टि के बिना कॉल करके आपका धन व्‍यय कर सकते हैं."</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="9178228524507610486">"फ़ोन की स्‍थिति और पहचान पढ़ें"</string>
     <string name="permdesc_readPhoneState" msgid="1639212771826125528">"एप्स को उपकरण की फ़ोन सुविधाओं तक पहुंचने देता है. यह अनुमति एप्स को फ़ोन नंबर और उपकरण आईडी, कॉल सक्रिय है या नहीं, और कॉल द्वारा कनेक्ट किया गया दूरस्‍थ नंबर निर्धारित करने देती है."</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"टेबलेट को निष्‍क्रिय होने से रोकें"</string>
@@ -561,32 +561,32 @@
     <string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"एप्लिकेशन को फ़ोन के इंफ़्रारेड ट्रांसमीटर का उपयोग करने देती है."</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="3278608165977736538">"अपने वॉलपेपर का आकार एडजस्ट करें"</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="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">"खाता प्रबंधक सेवा के रूप में कार्य करें"</string>
-    <string name="permdesc_accountManagerService" msgid="1948455552333615954">"एप्‍स को खाता प्रमाणकों को कॉल करने देता है."</string>
+    <string name="permdesc_accountManagerService" msgid="1948455552333615954">"ऐप्स को खाता प्रमाणकों को कॉल करने देता है."</string>
     <string name="permlab_getAccounts" msgid="1086795467760122114">"उपकरण पर खाते ढूंढें"</string>
     <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"एप्स को टेबलेट द्वारा ज्ञात खातों की सूची प्राप्‍त करने देता है. इसमें वे खाते शामिल हो सकते हैं जिन्‍हें आपके द्वारा इंस्‍टॉल किए गए एप्स ने बनाया है."</string>
     <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"एप्स को फ़ोन द्वारा ज्ञात खातों की सूची प्राप्‍त करने देता है. इसमें वे खाते शामिल हो सकते हैं जिन्‍हें आपके द्वारा इंस्‍टॉल किए गए एप्स ने बनाया है."</string>
     <string name="permlab_authenticateAccounts" msgid="5265908481172736933">"खाते बनाएं और पासवर्ड सेट करें"</string>
     <string name="permdesc_authenticateAccounts" msgid="5472124296908977260">"एप्‍िलकेशन को खाता बनाने और उनके पासवर्ड प्राप्त करने और सेट करने सहित, खाता प्रबंधक की खाता प्रमाणक क्षमताओं का उपयोग करने देता है."</string>
     <string name="permlab_manageAccounts" msgid="4983126304757177305">"खाते जोडें या निकालें"</string>
-    <string name="permdesc_manageAccounts" msgid="8698295625488292506">"एप्‍स को खाते जोड़ना और निकालना और उनके पासवर्ड हटाने जैसे कार्य करने देता है."</string>
+    <string name="permdesc_manageAccounts" msgid="8698295625488292506">"ऐप्स को खाते जोड़ना और निकालना और उनके पासवर्ड हटाने जैसे कार्य करने देता है."</string>
     <string name="permlab_useCredentials" msgid="235481396163877642">"उपकरण पर खातों का उपयोग करें"</string>
     <string name="permdesc_useCredentials" msgid="7984227147403346422">"एप्स को प्रमाणीकरण टोकन का अनुरोध करने देता है."</string>
     <string name="permlab_accessNetworkState" msgid="4951027964348974773">"नेटवर्क कनेक्‍शन देखें"</string>
@@ -598,9 +598,9 @@
     <string name="permlab_changeNetworkState" msgid="958884291454327309">"नेटवर्क कनेक्‍टिविटी बदलें"</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="5202012949247040011">"Wi-Fi कनेक्‍शन देखें"</string>
     <string name="permdesc_accessWifiState" msgid="5002798077387803726">"एप्स को Wi-Fi नेटवर्क के बारे में जानकारी, जैसे WI-Fi सक्षम है या नहीं और कनेक्‍ट किए गए Wi-Fi उपकरणों के नाम, देखने देता है."</string>
     <string name="permlab_changeWifiState" msgid="6550641188749128035">"Wi-Fi से कनेक्‍ट और डिस्‍कनेक्‍ट करें"</string>
@@ -609,23 +609,23 @@
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"एप्स को Wi-Fi नेटवर्क पर मल्टीकास्ट पते के उपयोग से केवल आपके टेबलेट पर ही नहीं, बल्कि सभी उपकरणों पर भेजे गए पैकेट प्राप्‍त करने देता है. यह गैर-मल्टीकास्ट मोड से अधिक पावर का उपयोग करता है."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"एप्स को Wi-Fi नेटवर्क पर मल्टीकास्ट पते के उपयोग से केवल आपके फ़ोन पर ही नहीं, बल्कि सभी उपकरणों पर भेजे गए पैकेट प्राप्‍त करने देता है. यह गैर-मल्टीकास्ट मोड से अधिक पावर का उपयोग करता है."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"Bluetooth सेटिंग पर पहुंचें"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"किसी एप्‍स को स्‍थानीय Bluetooth टेबलेट कॉन्‍फ़िगर करने की और रिमोट उपकरणों के साथ खोजने और युग्‍मित करने देता है."</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"एप्‍स को स्‍थानीय Bluetooth फ़ोन कॉन्‍फ़िगर करने देता है, और रिमोट उपकरणों के साथ खोजने और युग्‍मित करने देता है."</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"किसी ऐप्स को स्‍थानीय Bluetooth टेबलेट कॉन्‍फ़िगर करने की और रिमोट उपकरणों के साथ खोजने और युग्‍मित करने देता है."</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"ऐप्स को स्‍थानीय Bluetooth फ़ोन कॉन्‍फ़िगर करने देता है, और रिमोट उपकरणों के साथ खोजने और युग्‍मित करने देता है."</string>
     <string name="permlab_bluetoothPriv" msgid="4009494246009513828">"एप्‍लिकेशन के द्वारा Bluetooth युग्‍मन करने देती है"</string>
     <string name="permdesc_bluetoothPriv" product="tablet" msgid="8045735193417468857">"एप्‍लिकेशन को उपयोगकर्ता के इंटरैक्शन के बिना दूरस्‍थ उपकरणों के साथ युग्‍मित करने देती है."</string>
     <string name="permdesc_bluetoothPriv" product="default" msgid="8045735193417468857">"एप्‍लिकेशन को उपयोगकर्ता के इंटरैक्शन के बिना दूरस्‍थ उपकरणों के साथ युग्‍मित करने देती है."</string>
     <string name="permlab_accessWimaxState" msgid="4195907010610205703">"WiMAX से कनेक्ट और डिस्कनेक्ट करें"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"एप्स को WiMAX सक्षम है या नहीं और कनेक्‍ट किए गए किसी WiMAX नेटवर्क के बारे में जानकारी निर्धारित करने देता है."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"WiMAX स्‍थिति बदलें"</string>
-    <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"एप्‍स को WiMAX नेटवर्क से टेबलेट को कनेक्‍ट और डिस्‍कनेक्‍ट करने देता है."</string>
-    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"एप्‍स को WiMAX नेटवर्क से फ़ोन को कनेक्‍ट और डिस्‍कनेक्‍ट करने देता है."</string>
+    <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"ऐप्स को WiMAX नेटवर्क से टेबलेट को कनेक्‍ट और डिस्‍कनेक्‍ट करने देता है."</string>
+    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"ऐप्स को WiMAX नेटवर्क से फ़ोन को कनेक्‍ट और डिस्‍कनेक्‍ट करने देता है."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"Bluetooth उपकरणों के साथ युग्मित करें"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"एप्‍स को टेबलेट पर Bluetooth का कॉन्‍फ़िगरेशन देखने, और युग्‍मित उपकरणों के साथ कनेक्‍शन बनाने और स्‍वीकार करने देता है."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"एप्‍स को फ़ोन पर Bluetooth का कॉन्‍फ़िगरेशन देखने, और युग्‍मित उपकरणों के साथ कनेक्‍शन बनाने और स्‍वीकार करने देता है."</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"ऐप्स को टेबलेट पर Bluetooth का कॉन्‍फ़िगरेशन देखने, और युग्‍मित उपकरणों के साथ कनेक्‍शन बनाने और स्‍वीकार करने देता है."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"ऐप्स को फ़ोन पर Bluetooth का कॉन्‍फ़िगरेशन देखने, और युग्‍मित उपकरणों के साथ कनेक्‍शन बनाने और स्‍वीकार करने देता है."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"नियर फ़ील्‍ड कम्‍यूनिकेशन नियंत्रित करें"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"एप्स को नियर फ़ील्ड कम्यूनिकेशन (NFC) टैग, कार्ड, और रीडर के साथ संचार करने देता है."</string>
     <string name="permlab_disableKeyguard" msgid="3598496301486439258">"अपना स्‍क्रीन लॉक अक्षम करें"</string>
-    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"एप्‍स को कीलॉक और कोई भी संबद्ध पासवर्ड सुरक्षा अक्षम करने देता है. उदाहरण के लिए, इनकमिंग फ़ोन कॉल प्राप्त करते समय फ़ोन, कीलॉक को अक्षम कर देता है, फिर कॉल समाप्त होने पर कीलॉक को पुन: सक्षम कर देता है."</string>
+    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"ऐप्स को कीलॉक और कोई भी संबद्ध पासवर्ड सुरक्षा अक्षम करने देता है. उदाहरण के लिए, इनकमिंग फ़ोन कॉल प्राप्त करते समय फ़ोन, कीलॉक को अक्षम कर देता है, फिर कॉल समाप्त होने पर कीलॉक को पुन: सक्षम कर देता है."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"समन्वयन सेटिंग पढ़ें"</string>
     <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"एप्स को किसी खाते की समन्वयन सेटिंग पढ़ने देता है. उदाहरण के लिए, इससे यह निर्धारित किया जा सकता है कि लोग एप्स किसी खाते के साथ समन्‍वयित है या नहीं."</string>
     <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"समन्‍वयन बंद या चालू टॉगल करें"</string>
@@ -633,11 +633,11 @@
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"समन्वयन आंकड़े पढ़ें"</string>
     <string name="permdesc_readSyncStats" msgid="1510143761757606156">"एप्स को किसी खाते के समन्वयन आंकड़े, साथ ही समन्‍वयित ईवेंट का इतिहास और समन्‍वयित डेटा की मात्रा पढ़ने देता है."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"ग्राहकी-प्राप्त फ़ीड पढ़ें"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"एप्‍स को वर्तमान में समन्वयित फ़ीड के बारे में विवरण प्राप्त करने देता है."</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"ऐप्स को वर्तमान में समन्वयित फ़ीड के बारे में विवरण प्राप्त करने देता है."</string>
     <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"ग्राहकी-प्राप्त फ़ीड लिखें"</string>
     <string name="permdesc_subscribedFeedsWrite" msgid="6928930188826089413">"एप्स को आपके वर्तमान समन्वयित फ़ीड को संशोधित करने देता है. दुर्भावनापूर्ण एप्स आपके समन्वयित फ़ीड को बदल सकते है."</string>
     <string name="permlab_readDictionary" msgid="4107101525746035718">"शब्दकोश में आपके द्वारा जोड़े गए शब्‍दों को पढ़ें"</string>
-    <string name="permdesc_readDictionary" msgid="659614600338904243">"एप्‍स को ऐसे सभी शब्‍दों, नामों और वाक्यांशों को पढ़ने देता है जो संभवत: उपयोगकर्ता द्वारा उपयोगकर्ता ‍शब्दकोश में संग्रहीत किए गए हों."</string>
+    <string name="permdesc_readDictionary" msgid="659614600338904243">"ऐप्स को ऐसे सभी शब्‍दों, नामों और वाक्यांशों को पढ़ने देता है जो संभवत: उपयोगकर्ता द्वारा उपयोगकर्ता ‍शब्दकोश में संग्रहीत किए गए हों."</string>
     <string name="permlab_writeDictionary" msgid="2183110402314441106">"उपयोगकर्ता द्वारा परिभाषित शब्दकोश में शब्द जोड़ें"</string>
     <string name="permdesc_writeDictionary" msgid="8185385716255065291">"एप्स को उपयोगकर्ता शब्दकोश में नए शब्द लिखने देता है."</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"अपने USB संग्रहण की सामग्री पढ़ें"</string>
@@ -655,17 +655,17 @@
     <string name="permlab_sdcardAccessAll" msgid="8150613823900460576">"सभी उपयोगकर्ताओं के बाहरी संग्रहण तक पहुंचें"</string>
     <string name="permdesc_sdcardAccessAll" msgid="3215208357415891320">"एप्स को सभी उपयोगकर्ताओं के बाहरी संग्रहण तक पहुंचने दें."</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="permlab_bind_call_service" msgid="6724009726671246551">"इन-कॉल स्क्रीन से सहभागिता करें"</string>
     <string name="permdesc_bind_call_service" msgid="8732547662442572435">"एप्लिकेशन को यह नियंत्रित करने देती है कि उपयोगकर्ता को इन-कॉल स्क्रीन कब और कैसी दिखाई देती है."</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="permlab_markNetworkSocket" msgid="3658527214914959749">"सॉकेट मार्क बदलें"</string>
     <string name="permdesc_markNetworkSocket" msgid="7655568433696356578">"एप्स को रूटिंग के लिए सॉकेट मार्क बदलने देता है"</string>
     <string name="permlab_accessNotifications" msgid="7673416487873432268">"सूचनाओं तक पहुंचें"</string>
@@ -673,7 +673,7 @@
     <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"सूचना श्रवणकर्ता सेवा से जुड़ें"</string>
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"धारक को सूचना श्रवणकर्ता सेवा के शीर्ष स्तरीय इंटरफ़ेस से जुड़ने देती है. सामान्य एप्स के लिए कभी भी आवश्यक नहीं होनी चाहिए."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"वाहक के द्वारा उपलब्ध कराया गया कॉन्फ़िगरेशन एप्स प्रारंभ करें"</string>
-    <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"धारक को वाहक के द्वारा उपलब्ध कराया गया कॉन्फ़िगरेशन एप्स प्रारंभ करने देता है. सामान्‍य एप्‍स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
+    <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"धारक को वाहक के द्वारा उपलब्ध कराया गया कॉन्फ़िगरेशन एप्स प्रारंभ करने देता है. सामान्‍य ऐप्स के लिए कभी भी आवश्‍यक नहीं होना चाहिए."</string>
     <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"नेटवर्क स्थितियों के अवलोकनों को सुनें"</string>
     <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"एप्स को नेटवर्क स्थितियों के अवलोकनों को सुनने देता है. सामान्य एप्स के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"पासवर्ड नियम सेट करें"</string>
@@ -693,7 +693,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 name="policylab_disableKeyguardFeatures" msgid="266329104542638802">"कीगार्ड में सुविधाएं अक्षम करें"</string>
@@ -947,15 +947,15 @@
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"एप्स को आपके टेबलेट में संग्रहीत ब्राउज़र के इतिहास या बुकमार्क को संशोधित करने देता है. इससे एप्स ब्राउज़र डेटा को मिटा सकता है या संशोधित कर सकता है. ध्‍यान दें: यह अनुमति तृतीय-पक्ष ब्राउज़र या वेब ब्राउज़िंग क्षमताओं वाले अन्‍य एप्स द्वारा लागू नहीं की जा सकती."</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"एप्स को आपके फ़ोन में संग्रहीत ब्राउज़र के इतिहास या बुकमार्क को संशोधित करने देता है. इससे एप्स ब्राउज़र डेटा को मिटा सकता है या संशोधित कर सकता है. ध्‍यान दें: यह अनुमति तृतीय-पक्ष ब्राउज़र या वेब ब्राउज़िंग क्षमताओं वाले अन्‍य एप्स द्वारा लागू नहीं की जा सकती."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"अलार्म सेट करें"</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="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="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>
@@ -1122,7 +1122,7 @@
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"सिस्‍टम सेटिंग &gt; Apps &gt; डाउनलोड किए गए में डिफ़ॉल्‍ट साफ करें."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"कोई क्रिया चुनें"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"USB उपकरण के लिए कोई एप्स चुनें"</string>
-    <string name="noApplications" msgid="2991814273936504689">"कोई भी एप्‍स यह कार्यवाही नहीं कर सकता."</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>
@@ -1141,20 +1141,20 @@
     <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; Apps &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_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_1">%2$d</xliff:g> में से <xliff:g id="NUMBER_0">%1$d</xliff:g> एप्स अनुकूलित हो रहा है."</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_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_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>
@@ -1249,7 +1249,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>
@@ -1298,9 +1298,9 @@
     <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"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="permlab_route_media_output" msgid="1642024455750414694">"मीडिया आउटपुट को रूट करें"</string>
     <string name="permdesc_route_media_output" msgid="4932818749547244346">"एप्स को मीडिया आउटपुट को अन्य बाहरी उपकरणों पर रूट करने देता है."</string>
     <string name="permlab_access_keyguard_secure_storage" msgid="7565552237977815047">"कीगार्ड सुरक्षित संग्रहण एक्सेस करें"</string>
@@ -1318,7 +1318,7 @@
     <string name="ime_action_default" msgid="2840921885558045721">"निष्‍पादित करें"</string>
     <string name="dial_number_using" msgid="5789176425167573586">"<xliff:g id="NUMBER">%s</xliff:g> के उपयोग द्वारा \n नंबर डायल करें"</string>
     <string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g> का उपयोग करके\n संपर्क बनाएं"</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>
@@ -1415,7 +1415,7 @@
     <string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"Mode change"</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>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g> सेकंड में पुन: प्रयास करें"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"बाद में पुनः प्रयास करें"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"पूर्ण स्क्रीन से बाहर आने हेतु ऊपर से नीचे स्वाइप करें"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"पूर्ण स्क्रीन से बाहर आने के लिए ऊपर से नीचे स्वाइप करें."</string>
     <string name="done_label" msgid="2093726099505892398">"पूर्ण"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"घंटो का चक्राकार स्लाइडर"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"मिनटों का चक्राकार स्लाइडर"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index a1a73b0..acfdcc8 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Ponovite za <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Pokušajte ponovo kasnije"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Prijeđite prstom s vrha prema dolje za izlaz"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Prijeđite prstom s vrha prema dolje za izlaz iz cijelog zaslona."</string>
     <string name="done_label" msgid="2093726099505892398">"Gotovo"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kružni klizač sati"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kružni klizač minuta"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index e3c9db2..9301be1 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Próbálja újra <xliff:g id="COUNT">%d</xliff:g> másodperc múlva"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Próbálkozzon később"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"A kilépéshez húzza ujját a tetejétől lefelé"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"A teljes képernyős nézetből való kilépéshez húzza ujját a tetejétől lefelé."</string>
     <string name="done_label" msgid="2093726099505892398">"Kész"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Óra kör alakú csúszkája"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Perc kör alakú csúszkája"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index e8e2d1b..d599417 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -1116,7 +1116,7 @@
     <string name="loading" msgid="7933681260296021180">"Բեռնում..."</string>
     <string name="capital_on" msgid="1544682755514494298">"I"</string>
     <string name="capital_off" msgid="6815870386972805832">"O"</string>
-    <string name="whichApplication" msgid="4533185947064773386">"ավարտել գործողությունը` օգտագործելով"</string>
+    <string name="whichApplication" msgid="4533185947064773386">"Ավարտել գործողությունը` օգտագործելով"</string>
     <string name="whichHomeApplication" msgid="4616420172727326782">"Ընտրեք հիմնական հավելվածը"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"Օգտագործել լռելյայն այս գործողության համար:"</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Մաքրել լռելյայնը Համակարգի կարգավորումներ &gt; Ծրագրեր &gt;Ներբեռնված էջից:"</string>
@@ -1144,7 +1144,7 @@
     <string name="smv_application" msgid="3307209192155442829">"<xliff:g id="APPLICATION">%1$s</xliff:g> ծրագիրը (գործընթաց <xliff:g id="PROCESS">%2$s</xliff:g>) խախտել է իր ինքնահարկադրված Խիստ ռեժիմ  քաղաքականությունը:"</string>
     <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> գործընթացը խախտել է իր ինքնահարկադրված Խիստ ռեժիմ քաղաքականությունը:"</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_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_complete" msgid="1405954754112999229">"Բեռնումն ավարտվում է:"</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>-ն աշխատում է"</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Կրկին փորձեք <xliff:g id="COUNT">%d</xliff:g> վայրկյանից"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Կրկին փորձեք մի փոքր ուշ"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Սահահարվածեք վերից վար՝ ամբողջական էկրանից դուրս գալու համար"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Սահահարվածեք վերից վար՝ ամբողջական էկրանից դուրս գալու համար:"</string>
     <string name="done_label" msgid="2093726099505892398">"Պատրաստ է"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Ժամերի ընտրություն թվատախտակից"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Րոպեների ընտրություն թվատախտակից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index ab03cc7..5c8d73f 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Coba <xliff:g id="COUNT">%d</xliff:g> detik lagi"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Coba lagi nanti"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Gesek ke bawah untuk keluar dari layar penuh"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Gesek dari atas ke bawah untuk keluar dari layar penuh."</string>
     <string name="done_label" msgid="2093726099505892398">"Selesai"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Penggeser putar jam"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Penggeser putar menit"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d75f703..4b535a1 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Riprova tra <xliff:g id="COUNT">%d</xliff:g> s."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Riprova più tardi"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Uscita schermo intero: scorri in basso da alto"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Scorri dall\'alto verso il basso per uscire dalla modalità schermo intero."</string>
     <string name="done_label" msgid="2093726099505892398">"Fine"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Dispositivo di scorrimento circolare per le ore"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Dispositivo di scorrimento circolare per i minuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 901aefa..8cd54cb 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"נסה שוב בעוד <xliff:g id="COUNT">%d</xliff:g> שניות"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"נסה שוב מאוחר יותר"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"החלק מטה מהחלק העליון כדי לצאת ממסך מלא"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"החלק מטה מהחלק העליון כדי לצאת ממסך מלא."</string>
     <string name="done_label" msgid="2093726099505892398">"בוצע"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"מחוון שעות מעגלי"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"מחוון דקות מעגלי"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 6a2f624..d556a21 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g>秒後に再試行"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"しばらくしてから再試行"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"全画面表示を終了するには、上から下にスワイプ"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"全画面表示を終了するには、上から下にスワイプ"</string>
     <string name="done_label" msgid="2093726099505892398">"完了"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"円形スライダー(時)"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"円形スライダー(分)"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index 5e707e4..721e66b 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"კიდევ ერთხელ სცადეთ <xliff:g id="COUNT">%d</xliff:g> წამში"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"სცადეთ მოგვიანებით"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"ჩამოასრიალეთ ზევიდან სრული ეკრანის დასახურად"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"ჩამოასრიალეთ ზევიდან სრული ეკრანის დასახურად."</string>
     <string name="done_label" msgid="2093726099505892398">"დასრულდა"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"საათების წრიული სლაიდერი"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"წუთების წრიული სლაიდერი"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index cbfb318..a387c2d 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"សូម​ព្យាយាម​ម្ដង​ទៀត​ក្នុង​រយៈពេល <xliff:g id="COUNT">%d</xliff:g> វិនាទី"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"សូម​ព្យាយាម​ម្ដងទៀត​នៅ​ពេល​ក្រោយ។"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"អូស​​​ចុះក្រោម ដើម្បី​ចេញ​ពី​ការ​បង្ហាញ​ពេញ​អេក្រង់"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"អូស​​​​ពីលើ​ចុះក្រោម ដើម្បី​ចេញ​ពី​ការ​បង្ហាញ​ពេញ​អេក្រង់។"</string>
     <string name="done_label" msgid="2093726099505892398">"រួចរាល់"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"គ្រាប់​រំកិល​រង្វង់​ម៉ោង"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"គ្រាប់​រំកិល​រង្វង់​នាទី"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index fea1961..e3aed77 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g>초 후에 다시 시도하세요."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"나중에 다시 시도"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"전체화면을 종료하려면 위에서 아래로 스와이프"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"전체화면을 종료하려면 위에서 아래로 스와이프하세요."</string>
     <string name="done_label" msgid="2093726099505892398">"완료"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"시간 원형 슬라이더"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"분 원형 슬라이더"</string>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index de1ae74..640ca1e 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -64,4 +64,7 @@
     <!-- New TimePicker dimensions. -->
     <dimen name="timepicker_left_side_width">250dip</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
+
 </resources>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 0528387..cb31ae1 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"ລອງໃໝ່ໃນອີກ <xliff:g id="COUNT">%d</xliff:g> ວິນາທີ"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"ລອງໃໝ່ອີກຄັ້ງໃນພາຍຫລັງ."</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"ປັດລົງຈາກເທິງສຸດເພື່ອອກຈາກໂໝດເຕັມໜ້າຈໍ"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"ປັດລົງມາຈາກທາງເທິງເພື່ອອອກຈາກໂໝດເຕັມໜ້າຈໍ."</string>
     <string name="done_label" msgid="2093726099505892398">"ແລ້ວໆ"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"ໂຕໝຸນປັບຊົ່ວໂມງ"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"ໂຕໝຸນປັບນາທີ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 6de5ea4..0345529 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Band. dar po <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Vėliau bandykite dar kartą"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Perbr. žemyn, kad išeit. iš viso ekr. rež."</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Perbraukite nuo viršaus žemyn, kad išeitumėte iš viso ekrano režimo"</string>
     <string name="done_label" msgid="2093726099505892398">"Atlikta"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Apskritas valandų slankiklis"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Apskritas minučių slankiklis"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 64a07d1..855da14 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Mēģ. vēl pēc <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Vēlāk mēģiniet vēlreiz."</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Lai izietu no pilnekr., velc. no augšas lejup."</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Lai izietu no pilnekrāna režīma, velciet no augšas uz leju."</string>
     <string name="done_label" msgid="2093726099505892398">"Gatavs"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Stundu apļveida slīdnis"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Minūšu apļveida slīdnis"</string>
diff --git a/core/res/res/values-mcc204/config.xml b/core/res/res/values-mcc204/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc204/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc206/config.xml b/core/res/res/values-mcc206/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc206/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc208-mnc01/config.xml b/core/res/res/values-mcc208-mnc01/config.xml
deleted file mode 100644
index 3b84ff2..0000000
--- a/core/res/res/values-mcc208-mnc01/config.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">Orange Internet,orange.fr,,,orange,orange,,,,,208,01,1,DUN</string>
-
-</resources>
diff --git a/core/res/res/values-mcc208/config.xml b/core/res/res/values-mcc208/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc208/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc214-mnc03/config.xml b/core/res/res/values-mcc214-mnc03/config.xml
deleted file mode 100644
index 4a51a2f..0000000
--- a/core/res/res/values-mcc214-mnc03/config.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">Orange Internet PC,internet,,,orange,orange,,,,,214,03,1,DUN</string>
-
-</resources>
diff --git a/core/res/res/values-mcc214-mnc07/config.xml b/core/res/res/values-mcc214-mnc07/config.xml
deleted file mode 100644
index b49ad74..0000000
--- a/core/res/res/values-mcc214-mnc07/config.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">Conexión Compartida,movistar.es,,,MOVISTAR,MOVISTAR,,,,,214,07,1,DUN</string>
-
-</resources>
diff --git a/core/res/res/values-mcc214/config.xml b/core/res/res/values-mcc214/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc214/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc216/config.xml b/core/res/res/values-mcc216/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc216/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc219/config.xml b/core/res/res/values-mcc219/config.xml
index 80f4e58..7ae82fa 100644
--- a/core/res/res/values-mcc219/config.xml
+++ b/core/res/res/values-mcc219/config.xml
@@ -29,7 +29,4 @@
         <item>"96"</item>
     </string-array>
 
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
 </resources>
diff --git a/core/res/res/values-mcc222-mnc01/config.xml b/core/res/res/values-mcc222-mnc01/config.xml
deleted file mode 100644
index 6bb1196..0000000
--- a/core/res/res/values-mcc222-mnc01/config.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">TIM WEB,ibox.tim.it,,,,,,,,,222,01,,DUN</string>
-</resources>
diff --git a/core/res/res/values-mcc222/config.xml b/core/res/res/values-mcc222/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc222/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc226/config.xml b/core/res/res/values-mcc226/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc226/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc228/config.xml b/core/res/res/values-mcc228/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc228/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc230/config.xml b/core/res/res/values-mcc230/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc230/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc231/config.xml b/core/res/res/values-mcc231/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc231/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc232/config.xml b/core/res/res/values-mcc232/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc232/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc234-mnc20/config.xml
similarity index 64%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc234-mnc20/config.xml
index 8d6d3b1..d602c9f 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc234-mnc20/config.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
+** You my obtain a copy of the License at
 **
 **     http://www.apache.org/licenses/LICENSE-2.0
 **
@@ -17,9 +17,12 @@
 */
 -->
 
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <!-- Configure mobile network MTU. Carrier specific value is set here.
+    -->
+    <integer name="config_mobile_mtu">1440</integer>
 
 </resources>
diff --git a/core/res/res/values-mcc234-mnc33/config.xml b/core/res/res/values-mcc234-mnc33/config.xml
index 175f76e..776b570 100644
--- a/core/res/res/values-mcc234-mnc33/config.xml
+++ b/core/res/res/values-mcc234-mnc33/config.xml
@@ -20,22 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds.  Do not translate. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">Consumer Broadband,consumerbroadband,,,,,,,,,234,33,,DUN</string>
-
     <!-- Don't use roaming icon for considered operators -->
     <string-array translatable="false" name="config_operatorConsideredNonRoaming">
         <item>23430</item>
diff --git a/core/res/res/values-mcc234/config.xml b/core/res/res/values-mcc234/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc234/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc235-mnc94/config.xml
similarity index 64%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc235-mnc94/config.xml
index 8d6d3b1..d602c9f 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc235-mnc94/config.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
+** You my obtain a copy of the License at
 **
 **     http://www.apache.org/licenses/LICENSE-2.0
 **
@@ -17,9 +17,12 @@
 */
 -->
 
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <!-- Configure mobile network MTU. Carrier specific value is set here.
+    -->
+    <integer name="config_mobile_mtu">1440</integer>
 
 </resources>
diff --git a/core/res/res/values-mcc238/config.xml b/core/res/res/values-mcc238/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc238/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc240/config.xml b/core/res/res/values-mcc240/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc240/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc242/config.xml b/core/res/res/values-mcc242/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc242/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc244/config.xml b/core/res/res/values-mcc244/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc244/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc246/config.xml b/core/res/res/values-mcc246/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc246/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc247/config.xml b/core/res/res/values-mcc247/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc247/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc248/config.xml b/core/res/res/values-mcc248/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc248/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc260/config.xml b/core/res/res/values-mcc260/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc260/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc262/config.xml b/core/res/res/values-mcc262/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc262/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc454-mnc03/config.xml b/core/res/res/values-mcc268-mnc03/config.xml
similarity index 91%
rename from core/res/res/values-mcc454-mnc03/config.xml
rename to core/res/res/values-mcc268-mnc03/config.xml
index c7dc960..0d5fe57 100644
--- a/core/res/res/values-mcc454-mnc03/config.xml
+++ b/core/res/res/values-mcc268-mnc03/config.xml
@@ -34,7 +34,7 @@
     <!-- String containing the apn value for tethering.  May be overriden by secure settings
          TETHER_DUN_APN.  Value is a comma separated series of strings:
          "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">3 Share,share.lte.three.com.hk,,,,,,,,,454,03,1,DUN</string>
+         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,270,,DUN" -->
+    <string translatable="false" name="config_tether_apndata">Optimus HotSpot,modem,,,,,,,,,268,03,,DUN</string>
 
 </resources>
diff --git a/core/res/res/values-mcc268/config.xml b/core/res/res/values-mcc268/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc268/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc270/config.xml b/core/res/res/values-mcc270/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc270/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc272/config.xml b/core/res/res/values-mcc272/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc272/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc274/config.xml b/core/res/res/values-mcc274/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc274/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc278/config.xml b/core/res/res/values-mcc278/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc278/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc280/config.xml b/core/res/res/values-mcc280/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc280/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc284/config.xml b/core/res/res/values-mcc284/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc284/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc286/config.xml b/core/res/res/values-mcc286/config.xml
index f73a523..d99d051 100644
--- a/core/res/res/values-mcc286/config.xml
+++ b/core/res/res/values-mcc286/config.xml
@@ -61,7 +61,4 @@
          to enable use of the new Release 9 tables for Indic languages. -->
     <!-- <integer-array name="config_sms_enabled_locking_shift_tables"></integer-array> -->
 
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
 </resources>
diff --git a/core/res/res/values-mcc293/config.xml b/core/res/res/values-mcc293/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc293/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc294/config.xml b/core/res/res/values-mcc294/config.xml
deleted file mode 100644
index 8d6d3b1..0000000
--- a/core/res/res/values-mcc294/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-*/
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
-</resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc310/config.xml
similarity index 92%
rename from core/res/res/values-mcc202/config.xml
rename to core/res/res/values-mcc310/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc310/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc311/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc311/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc311/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc312/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc312/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc312/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc313/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc313/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc313/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc314/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc314/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc314/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc315/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc315/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc315/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc316/config.xml
similarity index 92%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc316/config.xml
index 8d6d3b1..df398f9 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc316/config.xml
@@ -20,6 +20,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
+    <bool name="config_safe_media_volume_enabled">false</bool>
 
 </resources>
diff --git a/core/res/res/values-mcc454-mnc03/config.xml b/core/res/res/values-mcc334-mnc050/config.xml
similarity index 91%
copy from core/res/res/values-mcc454-mnc03/config.xml
copy to core/res/res/values-mcc334-mnc050/config.xml
index c7dc960..00c4155 100644
--- a/core/res/res/values-mcc454-mnc03/config.xml
+++ b/core/res/res/values-mcc334-mnc050/config.xml
@@ -35,6 +35,6 @@
          TETHER_DUN_APN.  Value is a comma separated series of strings:
          "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">3 Share,share.lte.three.com.hk,,,,,,,,,454,03,1,DUN</string>
+    <string translatable="false" name="config_tether_apndata">Modem,modem.iusacellgsm.mx,,,iusacellgsm,iusacellgsm,,,,,334,050,1,DUN</string>
 
 </resources>
diff --git a/core/res/res/values-mcc340-mnc01/config.xml b/core/res/res/values-mcc340-mnc01/config.xml
index fb71f3bc..bbab4ad 100644
--- a/core/res/res/values-mcc340-mnc01/config.xml
+++ b/core/res/res/values-mcc340-mnc01/config.xml
@@ -34,5 +34,5 @@
          TETHER_DUN_APN.  Value is a comma separated series of strings:
          "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">Orangeweb,orangeweb,,,,,,orange,orange,,340,01,1,DUN</string>
+    <string translatable="false" name="config_tether_apndata">Orangeweb,orangeweb,,,orange,orange,,,,,340,01,1,DUN</string>
 </resources>
diff --git a/core/res/res/values-mcc454-mnc00/config.xml b/core/res/res/values-mcc454-mnc00/config.xml
deleted file mode 100644
index c92b9c7..0000000
--- a/core/res/res/values-mcc454-mnc00/config.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You my obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-      <item>1</item>
-      <item>4</item>
-      <item>7</item>
-      <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string translatable="false" name="config_tether_apndata">1O1O tethering,lte.internet,,,,,,,,,454,00,3,DUN</string>
-
-</resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc510-mnc08/config.xml
similarity index 69%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc510-mnc08/config.xml
index 8d6d3b1..7b27554 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc510-mnc08/config.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,8 +18,9 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
+    <!-- call barring MMI code from TS 22.030 Annex B
+         Indonesia AXIS does not support Call Barring service
+         and "333" is used for other purpose -->
+    <string-array translatable="false" name="config_callBarringMMI">
+    </string-array>
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/core/res/res/values-mcc510-mnc89/config.xml
similarity index 68%
copy from core/res/res/values-mcc202/config.xml
copy to core/res/res/values-mcc510-mnc89/config.xml
index 8d6d3b1..82efecf 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/core/res/res/values-mcc510-mnc89/config.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -18,8 +18,9 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
+    <!-- call barring MMI code from TS 22.030 Annex B
+         Indonesia Hutchison does not support Call Barring service
+         and "333" is used for other purpose -->
+    <string-array translatable="false" name="config_callBarringMMI">
+    </string-array>
 </resources>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 5632f68..7c25299 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g> секундын дараа дахин оролдоно уу"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Дараа дахин оролдоно уу"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Бүтэн дэлгэцээс гарахын тулд дээрээс нь эхлэн доош шудрана уу"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Бүтэн дэлгэцээс гарахын тулд дээрээс нь эхлэн доош шудрана уу."</string>
     <string name="done_label" msgid="2093726099505892398">"Дууссан"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Цаг гүйлгэгч"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Минут гүйлгэгч"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index fac3cdd..48cd177 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Cuba <xliff:g id="COUNT">%d</xliff:g> saat lagi"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Cuba sebentar lagi"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Leret ke bawah untuk keluar dari skrin penuh"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Leret ke bawah dari atas untuk keluar dari skrin penuh."</string>
     <string name="done_label" msgid="2093726099505892398">"Selesai"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Penggelangsar bulatan jam"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Penggelangsar bulatan minit"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index a0ba2fc..755d4f7 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Prøv på nytt om <xliff:g id="COUNT">%d</xliff:g> sekunder"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Prøv på nytt senere"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Sveip ned for å avslutte fullskjermvisning"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Sveip ned fra toppen av skjermen for å gå ut av fullskjermvisningen."</string>
     <string name="done_label" msgid="2093726099505892398">"Ferdig"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Sirkulær glidebryter for timer"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Sirkulær glidebryter for minutter"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 7543d6b..2b855c5 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Probeer het over <xliff:g id="COUNT">%d</xliff:g> seconden opnieuw"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Probeer het later opnieuw"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Van boven omlaag vegen: voll. scherm sluiten"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Veeg omlaag vanaf de bovenkant om het volledige scherm te sluiten."</string>
     <string name="done_label" msgid="2093726099505892398">"Gereed"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Ronde schuifregelaar voor uren"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Ronde schuifregelaar voor minuten"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 137e7c2..fad6094 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Spróbuj za <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Spróbuj ponownie później"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Przesuń z góry w dół, by zamknąć pełny ekran"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Przesuń z góry w dół, by zamknąć pełny ekran."</string>
     <string name="done_label" msgid="2093726099505892398">"Gotowe"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kołowy suwak godzin"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kołowy suwak minut"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 0898456..0253517 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Tente em: <xliff:g id="COUNT">%d</xliff:g> seg"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Tente novamente mais tarde"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Deslize para baixo para sair do ecrã inteiro"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Deslize rapidamente para baixo para sair do ecrã inteiro."</string>
     <string name="done_label" msgid="2093726099505892398">"Concluído"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Controlo de deslize circular das horas"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Controlo de deslize circular dos minutos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 7328e4c..6f504f0 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Tente novamente em <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Tente novamente mais tarde"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Deslize para baixo para sair da tela inteira"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Deslize de cima para baixo para sair da tela inteira"</string>
     <string name="done_label" msgid="2093726099505892398">"Concluído"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Controle deslizante circular das horas"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Controle deslizante circular dos minutos"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 1fb3eb0..ff3e214 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -2736,7 +2736,8 @@
     <!-- no translation found for restr_pin_countdown:other (4730868920742952817) -->
     <!-- no translation found for restr_pin_try_later (973144472490532377) -->
     <skip />
-    <!-- no translation found for transient_navigation_confirmation (8554991488096662508) -->
+    <!-- no translation found for immersive_mode_confirmation (7227416894979047467) -->
+    <!-- no translation found for immersive_mode_confirmation (8554991488096662508) -->
     <skip />
     <!-- no translation found for done_label (2093726099505892398) -->
     <skip />
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 41c3053..0488d9e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Reîncercați în <xliff:g id="COUNT">%d</xliff:g> sec."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Reîncercați mai târziu"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Glisați în jos pt. a ieși din ecran complet"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Glisați în jos pentru a ieși din ecran complet."</string>
     <string name="done_label" msgid="2093726099505892398">"Terminat"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Selector circular pentru ore"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Selector circular pentru minute"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 5d4482a..1aa847a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Повтор через <xliff:g id="COUNT">%d</xliff:g> сек."</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Повторите попытку позже."</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Чтобы вернуться в обычный режим, проведите пальцем вниз"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Чтобы вернуться в обычный режим, проведите пальцем вниз."</string>
     <string name="done_label" msgid="2093726099505892398">"Готово"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Выбор часов на циферблате"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Выбор минут на циферблате"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 1e6e4e3..0c18cf8 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Skúste to zas o <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Skúste to znova neskôr"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Režim celej obraz. ukončíte posunutím nadol"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Režim celej obrazovky ukončíte posunutím nadol."</string>
     <string name="done_label" msgid="2093726099505892398">"Hotovo"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kruhový posúvač hodín"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kruhový posúvač minút"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 60fc325..7abc6c7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Poskusite znova čez <xliff:g id="COUNT">%d</xliff:g> s"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Poskusite znova pozneje"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Povlecite z vrha, da zaprete celozas. način"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Povlecite z vrha, da zaprete celozaslonski način."</string>
     <string name="done_label" msgid="2093726099505892398">"Dokončano"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Okrogli drsnik za ure"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Okrogli drsnik za minute"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 921ad69..eff0c91 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Покушајте опет за <xliff:g id="COUNT">%d</xliff:g> сек"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Покушајте поново касније"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Превуците надоле од врха за излаз из целог екрана"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Превуците прстом одозго надоле да бисте изашли из целог екрана."</string>
     <string name="done_label" msgid="2093726099505892398">"Готово"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Кружни клизач за сате"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Кружни клизач за минуте"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 89da6ed..7b1ecaf 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -832,7 +832,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Försök igen"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Försök igen"</string>
     <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Du har försökt låsa upp med Ansiktslås för många gånger"</string>
-    <string name="lockscreen_plugged_in" msgid="8057762828355572315">"Laddar (<xliff:g id="PERCENT">%%</xliff:g> <xliff:g id="NUMBER">%d</xliff:g>)"</string>
+    <string name="lockscreen_plugged_in" msgid="8057762828355572315">"Laddar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="321635745684060624">"Batteriet har laddats"</string>
     <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_low_battery" msgid="1482873981919249740">"Anslut din laddare."</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Försök igen om <xliff:g id="COUNT">%d</xliff:g> sekunder"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Försök igen senare"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Dra nedåt om du vill avbryta fullskärmsläget"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Dra nedåt om du vill avbryta fullskärmsläget."</string>
     <string name="done_label" msgid="2093726099505892398">"Klart"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Cirkelreglage för timmar"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Cirkelreglage för minuter"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 661dbcc..6a2e556 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -260,7 +260,7 @@
     <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"Huruhusu programu kupokea na kuchakata mawasiliano ya dharura. Idhini hii inapatikana tu kwa programu za mfumo."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"soma mawasiliano ya matangazo ya simu"</string>
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Huruhusu programu kusoma mawasiliano ya matangazo ya simu yaliyoingia kwenye kifaa chako. Arifa za matangazo ya simu huwasilishwa katika maeneo mengine ili kukuonya juu ya hali za dharura. Huenda programu hasidi zikatatiza utendajikazi au shughuli ya kifaa chako wakati matangazo ya simu ya dharura yameingia."</string>
-    <string name="permlab_sendSms" msgid="5600830612147671529">"tuma ujumbe wa SMS"</string>
+    <string name="permlab_sendSms" msgid="5600830612147671529">"kutuma SMS"</string>
     <string name="permdesc_sendSms" msgid="7094729298204937667">"Inaruhusu programu kutuma ujumbe wa SMS. Hii inaweza ikasababisha malipo yasiyotarajiwa. Programu hasidi zinaweza kukugharimu pesa kwa kutuma ujumbe bila uthibitisho wako."</string>
     <string name="permlab_sendRespondViaMessageRequest" msgid="8713889105305943200">"tuma matukio ya kujibu-kupitia-ujumbe"</string>
     <string name="permdesc_sendRespondViaMessageRequest" msgid="7107648548468778734">"Inaruhusu programu kutuma maombi kwa programu nyingine za ujumbe ili kushughulikia matukio ya kujibu-kupitia-ujumbe kwa simu zinazoingia."</string>
@@ -427,7 +427,7 @@
     <string name="permdesc_setPreferredApplications" msgid="4973986762241783712">"Inaruhusu programu kurekebisha programu unazopendelea. Programu hasidi zinaweza, polepole, kubadilisha programu zinazoendeshwa, na kuzifanya programu ulizo nazo kukusanya data ya kibinafsi kutoka kwako."</string>
     <string name="permlab_writeSettings" msgid="2226195290955224730">"rekebisha mipangilio ya mfumo"</string>
     <string name="permdesc_writeSettings" msgid="7775723441558907181">"Inaruhusu programu kurekebisha mipangilio ya mfumo wa data. Programu hasidi zinaweza kuvuruga usanidi wa mfumo wako."</string>
-    <string name="permlab_writeSecureSettings" msgid="204676251876718288">"rekebisha mipangilio ya mfumo salama"</string>
+    <string name="permlab_writeSecureSettings" msgid="204676251876718288">"kurekebisha mipangilio ya mfumo salama"</string>
     <string name="permdesc_writeSecureSettings" msgid="8159535613020137391">"Inaruhusu programu kurekebisha mipagilio ya mfumo wa data salama. Si ya kutumiwa na programu za kawaida."</string>
     <string name="permlab_writeGservices" msgid="2149426664226152185">"rekebisha ramani ya Google services"</string>
     <string name="permdesc_writeGservices" msgid="1287309437638380229">"Inaruhusu programu kurekebisha ramani ya huduma za Google. Si ya kutumiwa na programu za kawaida."</string>
@@ -580,14 +580,14 @@
     <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"Huruhusu programu kubadilisha saa za eneo katika simu."</string>
     <string name="permlab_accountManagerService" msgid="4829262349691386986">"tenda kama Huduma ya Meneja wa Akaunti"</string>
     <string name="permdesc_accountManagerService" msgid="1948455552333615954">"Huruhusu programu kupiga simu kwa Wathibitishaji Akaunti."</string>
-    <string name="permlab_getAccounts" msgid="1086795467760122114">"pata akaunti kwenye kifaa"</string>
+    <string name="permlab_getAccounts" msgid="1086795467760122114">"kupata akaunti kwenye kifaa"</string>
     <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"Inaruhusu programu kupata orodha ya akaunti zinazojulikana kwa kompyuta kibao. Hii inaweza kujumuisha akaunti zozote zilizoundwa na programu ambazo umesakinisha."</string>
     <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"Inaruhusu programu kupata orodha ya akaunti zinazojulikana kwa simu. Hii inaweza kujumuisha akaunti zozote zilizoundwa na programu ambazo umesakinisha."</string>
-    <string name="permlab_authenticateAccounts" msgid="5265908481172736933">"fungua akaunti na weka manenosiri"</string>
+    <string name="permlab_authenticateAccounts" msgid="5265908481172736933">"kufungua akaunti na kuweka manenosiri"</string>
     <string name="permdesc_authenticateAccounts" msgid="5472124296908977260">"Inaruhusu programu kutumia uwezo wa uthibitishaji akaunti wa KidhibitiAkaunti, ikiwa ni pamoja na kufungua akaunti na kupata na kuweka manenosiri ya akaunti hizo."</string>
-    <string name="permlab_manageAccounts" msgid="4983126304757177305">"ongeza au uondoe akaunti"</string>
+    <string name="permlab_manageAccounts" msgid="4983126304757177305">"kuongeza au kuondoa akaunti"</string>
     <string name="permdesc_manageAccounts" msgid="8698295625488292506">"Inaruhusu programu kutekeleza shughuli kama vile kuongeza na kutoa akaunti, na kufuta manenosiri yazo."</string>
-    <string name="permlab_useCredentials" msgid="235481396163877642">"tumia akaunti kwenye kifaa"</string>
+    <string name="permlab_useCredentials" msgid="235481396163877642">"kutumia akaunti zilizo kwenye kifaa"</string>
     <string name="permdesc_useCredentials" msgid="7984227147403346422">"Inaruhusu programu kuomba shuhuda za uthibitisho."</string>
     <string name="permlab_accessNetworkState" msgid="4951027964348974773">"Kuangalia mitandao"</string>
     <string name="permdesc_accessNetworkState" msgid="8318964424675960975">"Inaruhusu programu kuona taarifa kuhusu miunganisho ya mtandao kama vile mitandao ipi iliyopo na imeunganishwa."</string>
@@ -644,8 +644,8 @@
     <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"soma maudhui ya kadi yako ya SD"</string>
     <string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"Huruhusu programu kusoma maudhui ya hifadhi ya USB."</string>
     <string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"Huruhusu programu kusoma maudhui ya kadi yako ya SD."</string>
-    <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"rekebisha au ufute maudhui ya hifadhi yako ya USB"</string>
-    <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"rekebisha au ufute maudhui ya kadi yako ya SD"</string>
+    <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"kurekebisha au kufuta maudhui ya hifadhi yako ya USB"</string>
+    <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"kurekebisha au kufuta maudhui ya kadi yako ya SD"</string>
     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"Inaruhusu programu kuandikia hifadhi ya USB."</string>
     <string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"Inaruhusu programu kuandikia kadi ya SD."</string>
     <string name="permlab_mediaStorageWrite" product="default" msgid="6859839199706879015">"badilisha/futa maudhui ya hifadhi ya media ya ndani."</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Jaribu tena baada ya sekunde <xliff:g id="COUNT">%d</xliff:g>"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Jaribu tena baadaye"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Telezesha kidole kwa kasi chini kuanzia juu ili uondoke kwenye skrini kamili"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Telezesha kidole kwa kasi chini kuanzia juu ili uondoke kwenye skrini kamili."</string>
     <string name="done_label" msgid="2093726099505892398">"Imekamilika"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Kitelezi cha mviringo wa saa"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Kitelezi cha mviringo wa dakika"</string>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 52c230b..d21f9b7 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -113,4 +113,6 @@
     <!-- Margin around the various security views -->
     <dimen name="keyguard_muliuser_selector_margin">12dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 3e6394a..75a363a 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"ลองอีกใน <xliff:g id="COUNT">%d</xliff:g> วินาที"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"ลองอีกครั้งในภายหลัง"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"กวาดนิ้วจากบนลงล่างเพื่อออกจากโหมดเต็มหน้าจอ"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"กวาดนิ้วจากบนลงล่างเพื่อออกจากโหมดเต็มหน้าจอ"</string>
     <string name="done_label" msgid="2093726099505892398">"เสร็จสิ้น"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"ตัวเลื่อนหมุนระบุชั่วโมง"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"ตัวเลื่อนหมุนระบุนาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 8bc78f4..33bb75c 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Subukan muli sa <xliff:g id="COUNT">%d</xliff:g> seg"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Subukang muli sa ibang pagkakataon"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Mag-swipe pababa upang lumabas sa full screen"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Mag-swipe pababa mula sa itaas upang lumabas sa full screen."</string>
     <string name="done_label" msgid="2093726099505892398">"Tapos na"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Pabilog na slider ng mga oras"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Pabilog na slider ng mga minuto"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 71f297b..4a97390 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g> saniye içinde tekrar deneyin"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Daha sonra tekrar deneyin"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Tam ekrandan çıkmak için aşağıya hızlıca kaydırın"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Tam ekrandan çıkmak için yukarıdan aşağıya hızlıca kaydırın."</string>
     <string name="done_label" msgid="2093726099505892398">"Bitti"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Saat kaydırma çemberi"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Dakika kaydırma çemberi"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 8a52b780..17f6d85 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Повтор за <xliff:g id="COUNT">%d</xliff:g> с"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Спробуйте пізніше"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Проведіть пальцем зверху вниз, щоб зменшити"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Проведіть пальцем зверху вниз, щоб вийти з повноекранного режиму."</string>
     <string name="done_label" msgid="2093726099505892398">"Готово"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Вибір годин на циферблаті"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Вибір хвилин на циферблаті"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 06437dd..4c064ec 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Hãy thử lại sau <xliff:g id="COUNT">%d</xliff:g> giây"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Hãy thử lại sau"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Vuốt từ trên xuống để thoát toàn màn hình"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Vuốt từ trên xuống để thoát toàn màn hình."</string>
     <string name="done_label" msgid="2093726099505892398">"Xong"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Thanh trượt giờ hình tròn"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Thanh trượt phút hình tròn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 6b14f35..c631931 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g>秒后重试"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"稍后重试"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"从顶部向下滑动即可退出全屏模式"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"从顶部向下滑动即可退出全屏模式。"</string>
     <string name="done_label" msgid="2093726099505892398">"完成"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"小时转盘"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"分钟转盘"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 49dede4..4aea26c 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g> 秒後再試一次"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"稍後再試"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"從頂端往下快速滑動即可退出全螢幕"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"從頂端往下快速滑動即可退出全螢幕。"</string>
     <string name="done_label" msgid="2093726099505892398">"完成"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"小時環形滑桿"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"分鐘環形滑桿"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 540f7e4..aa0d709 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -255,7 +255,7 @@
     <string name="permlab_receiveSms" msgid="8673471768947895082">"接收簡訊 (SMS)"</string>
     <string name="permdesc_receiveSms" msgid="6424387754228766939">"允許應用程式接收和處理簡訊。這項設定可讓應用程式監控傳送至您裝置的訊息,或在您閱讀訊息前擅自刪除訊息。"</string>
     <string name="permlab_receiveMms" msgid="1821317344668257098">"接收簡訊 (MMS)"</string>
-    <string name="permdesc_receiveMms" msgid="533019437263212260">"允許應用程式接收和處理 MMS 訊息。這項設定可讓應用程式監控傳送至您裝置的訊息,或在您閱讀訊息前擅自刪除訊息。"</string>
+    <string name="permdesc_receiveMms" msgid="533019437263212260">"允許應用程式接收和處理多媒體訊息。這項設定可讓應用程式監控傳送至您裝置的訊息,或在您閱讀訊息前擅自刪除訊息。"</string>
     <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"接收緊急廣播"</string>
     <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"允許應用程式接收及處理緊急廣播訊息,只有系統應用程式可以具備這項權限。"</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"讀取區域廣播訊息"</string>
@@ -761,7 +761,7 @@
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"公司行動電話"</string>
     <string name="phoneTypeWorkPager" msgid="649938731231157056">"公司呼叫器"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"助理"</string>
-    <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
+    <string name="phoneTypeMms" msgid="7254492275502768992">"多媒體簡訊"</string>
     <string name="eventTypeCustom" msgid="7837586198458073404">"自訂"</string>
     <string name="eventTypeBirthday" msgid="2813379844211390740">"生日"</string>
     <string name="eventTypeAnniversary" msgid="3876779744518284000">"週年紀念日"</string>
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"<xliff:g id="COUNT">%d</xliff:g> 秒後再試一次"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"稍後再試"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"從頂端往下滑動即可結束全螢幕"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"從頂端往下滑動即可結束全螢幕。"</string>
     <string name="done_label" msgid="2093726099505892398">"完成"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"小時數環狀滑桿"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"分鐘數環狀滑桿"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index bb9861b..d108539 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1651,7 +1651,7 @@
     <item quantity="other" msgid="4730868920742952817">"Zama futhi kumasekhondi angu-<xliff:g id="COUNT">%d</xliff:g>"</item>
   </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Zama futhi emva kwesikhathi"</string>
-    <string name="transient_navigation_confirmation" msgid="8554991488096662508">"Swayipha ngezansi kusuka ngaphezulu ukuze uphume kusikrini esigcwele"</string>
+    <string name="immersive_mode_confirmation" msgid="7227416894979047467">"Swayiphela phansi kusukela phezulu ukuze uphume kusikrini esigcwele."</string>
     <string name="done_label" msgid="2093726099505892398">"Kwenziwe"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Amahora weslayidi esiyindingilizi"</string>
     <string name="minute_picker_description" msgid="8606010966873791190">"Amaminithi weslayidi esiyindingilizi"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f0fc831..9dcefee 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2635,6 +2635,9 @@
         <!-- Fully qualified class name of an activity that allows the user to manually
              add printers to this print service. -->
         <attr name="addPrintersActivity" format="string"/>
+        <!-- Fully qualified class name of an activity with advanced print options
+             specific to this print service. -->
+        <attr name="advancedPrintOptionsActivity" format="string"/>
         <!-- The vendor name if this print service is vendor specific. -->
         <attr name="vendor" format="string"/>
     </declare-styleable>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 14a817e..7cd2582 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -78,6 +78,7 @@
 
     <drawable name="input_method_fullscreen_background">#fff9f9f9</drawable>
     <drawable name="input_method_fullscreen_background_holo">@drawable/screen_background_holo_dark</drawable>
+    <color name="input_method_navigation_guard">#ff000000</color>
 
     <!-- For date picker widget -->
     <drawable name="selected_day_background">#ff0092f4</drawable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 91a15a6..db16e55 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -594,6 +594,9 @@
     <!-- Disable lockscreen translucent decor by default -->
     <bool name="config_enableLockScreenTranslucentDecor">false</bool>
 
+    <!-- Enable translucent decor by default -->
+    <bool name="config_enableTranslucentDecor">true</bool>
+
     <!-- Enable puk unlockscreen by default.
          If unlock screen is disabled, the puk should be unlocked through Emergency Dialer -->
     <bool name="config_enable_puk_unlock_screen">true</bool>
@@ -1154,7 +1157,7 @@
     <bool name="config_useDevInputEventForAudioJack">false</bool>
 
     <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">false</bool>
+    <bool name="config_safe_media_volume_enabled">true</bool>
 
     <!-- Set to true if the wifi display supports compositing content stored
          in gralloc protected buffers.  For this to be true, there must exist
@@ -1265,8 +1268,8 @@
     </string-array>
 
     <!-- Threshold (in ms) under which a screen off / screen on will be considered a reset of the
-         transient navigation confirmation prompt.-->
-    <integer name="config_transient_navigation_confirmation_panic">5000</integer>
+         immersive mode confirmation prompt.-->
+    <integer name="config_immersive_mode_confirmation_panic">5000</integer>
 
     <!-- For some operators, PDU has garbages. To fix it, need to use valid index -->
     <integer name="config_valid_wappush_index">-1</integer>
@@ -1280,4 +1283,15 @@
          To do this, add 40483 item to values-mcc404-mnc85/config.xml -->
     <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming">
     </string-array>
+    <!-- call barring MMI code from TS 22.030 Annex B -->
+    <string-array translatable="false" name="config_callBarringMMI">
+        <item>33</item>
+        <item>331</item>
+        <item>332</item>
+        <item>35</item>
+        <item>351</item>
+        <item>330</item>
+        <item>333</item>
+        <item>353</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 82c088e..db5d01f 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -53,7 +53,7 @@
     <!-- Minimum size of the fastscroll overlay -->
     <dimen name="fastscroll_overlay_size">104dp</dimen>
     <!-- Text size of the fastscroll overlay -->
-    <dimen name="fastscroll_overlay_text_size">24sp</dimen>
+    <dimen name="fastscroll_overlay_text_size">52sp</dimen>
     <!-- Padding of the fastscroll overlay -->
     <dimen name="fastscroll_overlay_padding">16dp</dimen>
     <!-- Width of the fastscroll thumb -->
@@ -381,4 +381,7 @@
     <dimen name="timepicker_minimum_margin_top_bottom">24dip</dimen>
     <dimen name="timepicker_radial_picker_dimen">270dip</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">-1px</dimen>
+
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 752fbf1..dc111f5 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2080,6 +2080,7 @@
   <public type="attr" name="accessibilityLiveRegion" id="0x010103ee" />
   <public type="attr" name="windowTranslucentStatus" id="0x010103ef" />
   <public type="attr" name="windowTranslucentNavigation" id="0x010103f0" />
+  <public type="attr" name="advancedPrintOptionsActivity" id="0x10103f1"/>
 
   <public type="style" name="Theme.Holo.NoActionBar.TranslucentDecor" id="0x010301e1" />
   <public type="style" name="Theme.Holo.Light.NoActionBar.TranslucentDecor" id="0x010301e2" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6d91833..e4ef219 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4491,8 +4491,8 @@
     <!-- PIN entry dialog tells the user to not enter a PIN for a while. [CHAR LIMIT=none] -->
     <string name="restr_pin_try_later">Try again later</string>
 
-    <!-- Toast bar message when hiding the transient navigation bar [CHAR LIMIT=45] -->
-    <string name="transient_navigation_confirmation">Swipe down from the top to exit full screen</string>
+    <!-- Cling help message when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
+    <string name="immersive_mode_confirmation" msgid="8554991488096662508">Swipe down from the top to exit full screen.</string>
 
     <!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
     <string name="done_label">Done</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 78dd838..177ab51 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -138,12 +138,6 @@
         <item name="windowExitAnimation">@anim/submenu_exit</item>
     </style>
 
-    <!-- {@hide} -->
-    <style name="Animation.ToastBar">
-        <item name="windowEnterAnimation">@anim/toast_bar_enter</item>
-        <item name="windowExitAnimation">@anim/toast_bar_exit</item>
-    </style>
-
     <style name="Animation.TypingFilter">
         <item name="windowEnterAnimation">@anim/grow_fade_in_center</item>
         <item name="windowExitAnimation">@anim/shrink_fade_out_center</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7b14368..37f9cb9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -292,6 +292,7 @@
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
+  <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
@@ -302,7 +303,6 @@
   <java-symbol type="integer" name="config_ntpThreshold" />
   <java-symbol type="integer" name="config_ntpTimeout" />
   <java-symbol type="integer" name="config_toastDefaultGravity" />
-  <java-symbol type="integer" name="config_transient_navigation_confirmation_panic" />
   <java-symbol type="integer" name="config_wifi_framework_scan_interval" />
   <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" />
   <java-symbol type="integer" name="config_wifi_scan_interval_p2p_connected" />
@@ -346,6 +346,7 @@
   <java-symbol type="dimen" name="notification_text_size" />
   <java-symbol type="dimen" name="notification_title_text_size" />
   <java-symbol type="dimen" name="notification_subtext_size" />
+  <java-symbol type="dimen" name="immersive_mode_cling_width" />
 
   <java-symbol type="string" name="add_account_button_label" />
   <java-symbol type="string" name="addToDictionary" />
@@ -938,7 +939,6 @@
   <java-symbol type="string" name="restr_pin_try_later" />
   <java-symbol type="string" name="write_fail_reason_cancelled" />
   <java-symbol type="string" name="write_fail_reason_cannot_write" />
-  <java-symbol type="string" name="transient_navigation_confirmation" />
   <java-symbol type="string" name="ssl_ca_cert_noti_by_unknown" />
   <java-symbol type="string" name="ssl_ca_cert_noti_managed" />
   <java-symbol type="string" name="ssl_ca_cert_warning" />
@@ -981,6 +981,7 @@
   <java-symbol type="array" name="config_disabledUntilUsedPreinstalledImes" />
   <java-symbol type="array" name="config_operatorConsideredNonRoaming" />
   <java-symbol type="array" name="config_sameNamedOperatorConsideredRoaming" />
+  <java-symbol type="array" name="config_callBarringMMI" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="indicator_input_error" />
@@ -1081,7 +1082,6 @@
   <java-symbol type="drawable" name="text_select_handle_left" />
   <java-symbol type="drawable" name="text_select_handle_middle" />
   <java-symbol type="drawable" name="text_select_handle_right" />
-  <java-symbol type="drawable" name="toast_bar_bg" />
   <java-symbol type="drawable" name="unknown_image" />
   <java-symbol type="drawable" name="unlock_default" />
   <java-symbol type="drawable" name="unlock_halo" />
@@ -1094,6 +1094,9 @@
   <java-symbol type="drawable" name="notification_template_icon_low_bg" />
   <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
   <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />
+  <java-symbol type="drawable" name="cling_button" />
+  <java-symbol type="drawable" name="cling_arrow_up" />
+  <java-symbol type="drawable" name="cling_bg" />
 
   <java-symbol type="layout" name="action_bar_home" />
   <java-symbol type="layout" name="action_bar_title_item" />
@@ -1170,7 +1173,6 @@
   <java-symbol type="layout" name="textview_hint" />
   <java-symbol type="layout" name="time_picker_legacy" />
   <java-symbol type="layout" name="time_picker_dialog" />
-  <java-symbol type="layout" name="toast_bar" />
   <java-symbol type="layout" name="transient_notification" />
   <java-symbol type="layout" name="volume_adjust" />
   <java-symbol type="layout" name="volume_adjust_item" />
@@ -1197,6 +1199,7 @@
   <java-symbol type="layout" name="app_not_authorized" />
   <java-symbol type="layout" name="restrictions_pin_challenge" />
   <java-symbol type="layout" name="restrictions_pin_setup" />
+  <java-symbol type="layout" name="immersive_mode_cling" />
 
   <java-symbol type="anim" name="slide_in_child_bottom" />
   <java-symbol type="anim" name="slide_in_right" />
@@ -1226,7 +1229,6 @@
   <java-symbol type="style" name="Animation.DropDownUp" />
   <java-symbol type="style" name="Animation.DropDownDown" />
   <java-symbol type="style" name="Animation.PopupWindow" />
-  <java-symbol type="style" name="Animation.ToastBar" />
   <java-symbol type="style" name="Animation.TypingFilter" />
   <java-symbol type="style" name="Animation.TypingFilterRestore" />
   <java-symbol type="style" name="Animation.Dream" />
@@ -1292,6 +1294,7 @@
   <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" />
   <java-symbol type="bool" name="config_enableLockScreenRotation" />
   <java-symbol type="bool" name="config_enableLockScreenTranslucentDecor" />
+  <java-symbol type="bool" name="config_enableTranslucentDecor" />
   <java-symbol type="bool" name="config_lidControlsSleep" />
   <java-symbol type="bool" name="config_reverseDefaultRotation" />
   <java-symbol type="bool" name="config_showNavigationBar" />
@@ -1442,6 +1445,7 @@
   <java-symbol type="bool" name="config_wimaxEnabled" />
   <java-symbol type="bool" name="show_ongoing_ime_switcher" />
   <java-symbol type="color" name="config_defaultNotificationColor" />
+  <java-symbol type="color" name="input_method_navigation_guard" />
   <java-symbol type="drawable" name="ic_notification_ime_default" />
   <java-symbol type="drawable" name="ic_notify_wifidisplay" />
   <java-symbol type="drawable" name="ic_menu_refresh" />
@@ -1614,7 +1618,7 @@
   <java-symbol type="anim" name="push_up_out" />
   <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
   <java-symbol type="anim" name="lock_screen_behind_enter" />
- 
+
   <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
   <java-symbol type="dimen" name="status_bar_icon_size" />
   <java-symbol type="dimen" name="system_bar_icon_size" />
@@ -1736,6 +1740,7 @@
   <!-- From Chromium-WebView -->
   <java-symbol type="attr" name="actionModeWebSearchDrawable" />
   <java-symbol type="string" name="websearch" />
+  <java-symbol type="drawable" name="ic_media_video_poster" />
 
   <!-- From SubtitleView -->
   <java-symbol type="dimen" name="subtitle_corner_radius" />
diff --git a/data/fonts/MTLmr3m.ttf b/data/fonts/MTLmr3m.ttf
index 76fe737..91dd47f 100644
--- a/data/fonts/MTLmr3m.ttf
+++ b/data/fonts/MTLmr3m.ttf
Binary files differ
diff --git a/docs/html/design/patterns/new.jd b/docs/html/design/patterns/new.jd
index 1fc4987..23e3e10 100644
--- a/docs/html/design/patterns/new.jd
+++ b/docs/html/design/patterns/new.jd
@@ -56,7 +56,7 @@
       <li>Trying it out yourself</li>
     </ul>
 
-    <p>You can supply separate landscape and portrait layouts for your widgets, which the system inflates as appropriate when the screen orientation changes. The [Application Widgets] (should be link) has useful details about widget types, limitations, and design considerations.</p>
+    <p>You can supply separate landscape and portrait layouts for your widgets, which the system inflates as appropriate when the screen orientation changes. The <a href="{@docRoot}design/patterns/widgets.html">Widgets</a> page has useful details about widget types, limitations, and design considerations.</p>
   </div>
   <div class="layout-content-col span-2">
     <img src="{@docRoot}design/media/new_accessibility.png">
diff --git a/docs/html/design/videos/index.jd b/docs/html/design/videos/index.jd
index 8ddd4aa..91a784a 100644
--- a/docs/html/design/videos/index.jd
+++ b/docs/html/design/videos/index.jd
@@ -1,7 +1,70 @@
 page.title=Videos
 @jd:body
 
-<p>The Android Design Team was pleased to present five fantastic design-oriented sessions at Google I/O 2012. Visit these pages to view the videos and presentations from the conference.</p>
+<p>The Android Design Team presents design-oriented sessions at Google I/O every year. Visit these pages to view the videos and presentations from the conferences.</p>
+
+<img src="{@docRoot}images/home/io-logo-2013-alt.png">
+
+<div class="vspace size-2">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+    <h3 id="design-for-success"><a href="https://developers.google.com/events/io/2013/sessions/326368573">Enchant, Simplify, Amaze: Android's Design Principles</a></h3>
+    <p>Want to enchant people, simplify their lives, and make them feel amazing with your app? Learn how Android's Design Principles can help you create products that resonate with people. Find out about the meaning and research behind the principles. See real-world examples and practices from the Android Design team. Discover techniques for applying the principles in your daily work. No design experience necessary.</p>
+  </div>
+  <div class="layout-content-col span-6">
+    <iframe width="355" height="200" src="//www.youtube.com/embed/s0HIP8EdlnE" frameborder="0" allowfullscreen=""></iframe>
+  </div>
+</div>
+
+<div class="vspace size-2">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+    <h3 id="design-for-success"><a href="https://developers.google.com/events/io/2013/sessions/326301704">Structure in Android App Design</a></h3>
+    <p>Life is simple when your app is simple. But when your apps gets more complex, how do you choose between spinners, tabs, and drawers for navigation? Members of the Android Design team look at techniques for making your app predictable and pleasing to use.</p>
+  </div>
+  <div class="layout-content-col span-6">
+    <iframe width="355" height="200" src="//www.youtube.com/embed/XpqyiBR0lJ4" frameborder="0" allowfullscreen=""></iframe>
+  </div>
+</div>
+
+<div class="vspace size-2">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+    <h3 id="design-for-success"><a href="https://developers.google.com/events/io/2013/sessions/326425499">Fireside Chat with the Android Team</a></h3>
+    <p>Pull up a chair and join the Android platform team for a fireside chat. It's your opportunity to ask us about the platform and learn a little bit more about why things work the way they do, from the people who built it. </p>
+  </div>
+  <div class="layout-content-col span-6">
+    <iframe width="355" height="200" src="//www.youtube.com/embed/A5OOJDIrYls" frameborder="0" allowfullscreen=""></iframe>
+  </div>
+</div>
+
+<div class="vspace size-2">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+    <h3 id="design-for-success"><a href="https://developers.google.com/events/io/2013/sessions/326483138">Agile UX Research Practice in Android</a></h3>
+    <p>In the Android UX team, it is critical to get user feedback frequently and consistently so that we are able to iterate and develop the best-in-class designs for our users. We will discuss how the team applied ""Pulse Studies"" (iterative research sessions) in order to put new ideas, designs, and concepts in front of users on a regular basis; it requires minimal advance planning, it can have an immediate product impact, and it can meet urgent needs. </p>
+  </div>
+  <div class="layout-content-col span-6">
+    <iframe width="355" height="200" src="//www.youtube.com/embed/6MOeVNbh9cY" frameborder="0" allowfullscreen=""></iframe>
+  </div>
+</div>
+
+<div class="vspace size-2">&nbsp;</div>
+
+<div class="layout-content-row">
+  <div class="layout-content-col span-7">
+    <h3 id="design-for-success"><a href="https://developers.google.com/events/io/2013/sessions/326460111">Cognitive Science and Design</a></h3>
+    <p>This session will provide an in-depth look at human perception and cognition, and its implications for interactive and visual design. The human brain is purely treated as an information processing machine, and we will teach the audience its attributes, its advantages, its limitations, and generally how to hack it. </p>
+  </div>
+  <div class="layout-content-col span-6">
+    <iframe width="355" height="200" src="//www.youtube.com/embed/z2exxj4COhU" frameborder="0" allowfullscreen=""></iframe>
+  </div>
+</div>
+
 <img src="{@docRoot}design/media/extras_googleio_12.png">
 
 <div class="vspace size-2">&nbsp;</div>
diff --git a/docs/html/distribute/googleplay/spotlight/games.jd b/docs/html/distribute/googleplay/spotlight/games.jd
index 4e356db..1fbc03f 100644
--- a/docs/html/distribute/googleplay/spotlight/games.jd
+++ b/docs/html/distribute/googleplay/spotlight/games.jd
@@ -102,7 +102,7 @@
   width: 78px;
   float: left;
   margin: 12px 20px 30px 20px;"
-  src="//lh5.ggpht.com/l20dR2HYLV8vECoC35q_0NdfaAGTe4lZIFy_wCJRDqZjeQqSgneLRpXi3qOnnCaLXA=w124">
+  src="//lh4.ggpht.com/Q7mQJsdhulW4_s039R9aaRhQkGnyzLkhF00j5EnyhHOivijnyi7P7b5A8qG0xk1r-jQ=w124">
           
 <div style="list-style: none;height:100%;
   float: right;
diff --git a/docs/html/distribute/googleplay/spotlight/tablets.jd b/docs/html/distribute/googleplay/spotlight/tablets.jd
index cfea29a..7a98755 100644
--- a/docs/html/distribute/googleplay/spotlight/tablets.jd
+++ b/docs/html/distribute/googleplay/spotlight/tablets.jd
@@ -17,7 +17,90 @@
 expand their offering to include Android tablets.</p>
 
 
-<div style="margin-bottom:2em;"><!-- START STORY -->
+<div style="margin-bottom:2em;" id="rememberthemilk"><!-- START STORY -->
+
+<h3>Remember The Milk: Lifting installs with tablet design</h3>
+
+  <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px;
+            -moz-border-radius: 5px;
+            border-radius: 5px height:78px;
+            width: 78px;
+            float: left;
+            margin: 12px 20px 9px 20px;" src=
+            "//lh3.ggpht.com/xmnal18taauP2mjQFEhr1PhcItQ_W32IRuaD86IoL2U_4E-mfeKiliKtkISgOuA6Ln9n=w124">
+          
+  <div style="list-style: none;height:100%;
+  float: right;
+  border-top: 1px solid #9C0;
+  width: 220px;
+  margin: 4px 20px;padding: .5em;">
+  
+
+    <h5>About the app</h5> 
+    
+    
+    <ul>
+      <li><a href="//play.google.com/store/apps/details?id=com.rememberthemilk.MobileRTM">Remember The Milk</a></li>
+      <li>A feature-packed to-do list app; never forget the milk (or anything else) again</li>
+    </ul>
+
+    <h5>Tablet Results</h5> 
+
+    <ul>
+      <li>83% jump in tablet installs following update </li>
+      <li>Nexus 7 is most popular Android device for app </li>
+      <li>Single APK for phones and tablets</li>
+      </ul>
+    
+    <div style="padding:.5em 0 0 1em;">
+      <a href="//play.google.com/store/apps/details?id=com.rememberthemilk.MobileRTM">
+        <img alt="Android app on Google Play"
+         src="//developer.android.com/images/brand/en_generic_rgb_wo_45.png" />
+      </a>
+      
+    </div>
+  </div>
+
+  <div style="line-height:1.4em;">
+    <p style="margin-top:0;margin-bottom:12px;">When the Android tablet guidelines
+      came out in 2012, the team at Remember The Milk had already been thinking about
+      a redesign for their <a href="//play.google.com/store/apps/details?id=com.rememberthemilk.MobileRTM">feature-packed
+      to-do list app</a>. Omar Kilani, Co-founder of Remember The Milk, explains how
+      <a href="//blog.rememberthemilk.com/2013/04/the-all-new-remember-the-milk-for-android-and-tablets-too/">updating</a>
+      their app to meet the tablet guidelines lead to an 83% jump in tablet installs: </p>
+
+    <p>“We took this as an opportunity to think about how we were going to approach
+      Android tablets differently from a user experience perspective. The guidelines
+      were a helpful resource, and with the extra screen real estate tablets afford,
+      users have the opportunity to see all of their data in context and drill down
+      on more items. All of this is accomplished using a single APK on Play, even though
+      the phone and tablet versions each capture completely different use cases for us.”</p>
+
+    <p>“In the month after updating, we saw our tablet installs on Google Play jump 83%,
+      and the Nexus 7 is now the most popular Android device amongst our users. For us,
+      designing for tablets was an investment that has really paid off.”</p>
+
+    <p>The team also came out with a number of other goodies &mdash; including a new set of
+      widgets and richer notifications, and more ways to provide an immersive experience
+      for their users.</p>
+  </div>
+
+  <div style="clear:both;margin-top:30px;width:auto;">
+  
+    <img src="{@docRoot}images/distribute/rememberthemilk.png">
+
+    <div style="width:600px;margin-top:0px;padding:0 90px;">
+      <p class="image-caption"><span style="font-weight:500;">Tablet redesign led to lift
+      in installs</span>: Following the redesign of the Android app, in part to meet the tablet
+      design criteria, Remember The Milk saw an 83% increase in tablet installs.</p>
+    </div>
+
+  </div>
+
+</div> <!-- END STORY -->
+
+
+<div style="margin-bottom:2em;" id="mint"><!-- START STORY -->
 
 <h3>Mint: More screen real estate = more engagement</h3>
 
@@ -96,7 +179,7 @@
 
   <div style="clear:both;margin-top:40px;width:auto;">
   
-    <a href=""><img src="{@docRoot}images/distribute/mint.png"></a>
+    <img src="{@docRoot}images/distribute/mint.png">
 
     <div style="width:600px;margin-top:0px;padding:0 90px;">
       <p class="image-caption"><span style="font-weight:500;">Making the most of tablet screens</span>: Mint used the extra screen area on tablets to offer quick access to additional tools and information.</p>
@@ -184,7 +267,7 @@
 
   <div style="clear:both;margin-top:40px;width:auto;">
   
-    <a href=""><img src="{@docRoot}images/distribute/tinyvillage.png"></a>
+    <img src="{@docRoot}images/distribute/tinyvillage.png">
 
     <div style="width:600px;margin-top:0px;padding:0 90px;">
       <p class="image-caption"><span style="font-weight:500;">More monetization
@@ -268,7 +351,7 @@
 
   <div style="clear:both;margin-top:40px;width:auto;">
   
-    <a href=""><img src="{@docRoot}images/distribute/instapaper.png"></a>
+    <img src="{@docRoot}images/distribute/instapaper.png">
 
     <div style="width:600px;margin-top:0px;padding:0 90px;">
       <p class="image-caption"><span style="font-weight:500;">Popular with
diff --git a/docs/html/guide/appendix/media-formats.jd b/docs/html/guide/appendix/media-formats.jd
index 9070968..7c6c145 100644
--- a/docs/html/guide/appendix/media-formats.jd
+++ b/docs/html/guide/appendix/media-formats.jd
@@ -217,9 +217,17 @@
 </tr>
 
 <tr>
-<td>WEBP</td>
-<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 4.0+)</small></td>
-<td style="text-align: center;" nowrap><big>&bull;</big><br><small>(Android 4.0+)</small></td>
+<td>WebP</td>
+<td style="text-align: center;" nowrap>
+  <big>&bull;</big><br>
+  <small>(Android 4.0+)</small><br>
+  <small>(Lossless, Transparency, Android 4.2.1+)</small>
+</td>
+<td style="text-align: center;" nowrap>
+  <big>&bull;</big><br>
+  <small>(Android 4.0+)</small><br>
+  <small>(Lossless, Transparency, Android 4.2.1+)</small>
+</td>
 <td>&nbsp;</td>
 <td>
   WebP (.webp)</td>
diff --git a/docs/html/images/distribute/rememberthemilk.png b/docs/html/images/distribute/rememberthemilk.png
new file mode 100644
index 0000000..b170cf3
--- /dev/null
+++ b/docs/html/images/distribute/rememberthemilk.png
Binary files differ
diff --git a/docs/html/images/home/io-logo-2013-alt.png b/docs/html/images/home/io-logo-2013-alt.png
new file mode 100644
index 0000000..1cdb4f4
--- /dev/null
+++ b/docs/html/images/home/io-logo-2013-alt.png
Binary files differ
diff --git a/docs/html/images/tools/mat-histogram@2x.png b/docs/html/images/tools/mat-histogram@2x.png
new file mode 100644
index 0000000..feb9654
--- /dev/null
+++ b/docs/html/images/tools/mat-histogram@2x.png
Binary files differ
diff --git a/docs/html/images/tools/monitor-hprof@2x.png b/docs/html/images/tools/monitor-hprof@2x.png
new file mode 100644
index 0000000..7afb9c7
--- /dev/null
+++ b/docs/html/images/tools/monitor-hprof@2x.png
Binary files differ
diff --git a/docs/html/images/tools/monitor-tracker@2x.png b/docs/html/images/tools/monitor-tracker@2x.png
new file mode 100644
index 0000000..a0bc329
--- /dev/null
+++ b/docs/html/images/tools/monitor-tracker@2x.png
Binary files differ
diff --git a/docs/html/images/tools/monitor-vmheap@2x.png b/docs/html/images/tools/monitor-vmheap@2x.png
new file mode 100644
index 0000000..ef4b9c2
--- /dev/null
+++ b/docs/html/images/tools/monitor-vmheap@2x.png
Binary files differ
diff --git a/docs/html/tools/debugging/debugging-memory.jd b/docs/html/tools/debugging/debugging-memory.jd
new file mode 100644
index 0000000..0454293
--- /dev/null
+++ b/docs/html/tools/debugging/debugging-memory.jd
@@ -0,0 +1,494 @@
+page.title=Investigating Your RAM Usage
+page.tags="memory","OutOfMemoryError"
+@jd:body
+
+ <div id="qv-wrapper">
+    <div id="qv">
+      <h2>In this document</h2>
+<ol>
+  <li><a href="#LogMessages">Interpreting Log Messages</a></li>
+  <li><a href="#ViewHeap">Viewing Heap Updates</a></li>
+  <li><a href="#TrackAllocations">Tracking Allocations</a></li>
+  <li><a href="#ViewingAllocations">Viewing Overall Memory Allocations</a></li>
+  <li><a href="#HeapDump">Capturing a Heap Dump</a></li>
+  <li><a href="#TriggerLeaks">Triggering Memory Leaks</a></li>
+</ol>
+      <h2>See Also</h2>
+      <ul>
+        <li><a href="{@docRoot}training/articles/memory.html">Managing Your App's Memory</a></li>
+      </ul>
+    </div>
+  </div>
+
+
+
+
+<p>Because Android is designed for mobile devices, you should always be careful about how much
+random-access memory (RAM) your app uses. Although Android’s Dalvik virtual machine performs
+routine garbage collection, this doesn’t mean you can ignore when and where your app allocates and
+releases memory. In order to provide a stable user experience that allows the system to quickly
+switch between apps, it’s important that your app does not needlessly consume memory when the user
+is not interacting with it.</p>
+
+<p>Even if you follow all the best practices for <a href="{@docRoot}training/articles/memory.html"
+>Managing Your App Memory</a> during
+development (which you should), you still might leak objects or introduce other memory bugs. The
+only way to be certain your app is using as little memory as possible is to analyze your app’s
+memory usage with tools. This guide shows you how to do that.</p>
+
+
+<h2 id="LogMessages">Interpreting Log Messages</h2>
+
+<p>The simplest place to begin investigating your apps memory usage is the Dalvik log messages. You'll
+find these log messages in <a href="{@docRoot}tools/help/logcat.html">logcat</a> (the output is
+available in the Device Monitor or directly in IDEs such as Eclipse and Android Studio).</p>
+
+<p>Every time a garbage collection occurs, logcat prints a message with the following information:</p>
+
+<pre class="no-pretty-print">
+D/dalvikvm: &lt;GC_Reason> &lt;Amount_freed>, &lt;Heap_stats>, &lt;External_memory_stats>, &lt;Pause_time>
+</pre>
+
+<dl>
+<dt>GC Reason</dt>
+<dd>
+What triggered the garbage collection and what kind of collection it is. Reasons that may appear
+include:
+<dl>
+<dt><code>GC_CONCURRENT</code></dt>
+<dd>A concurrent garbage collection that frees up memory as your heap begins to fill up.</dd>
+
+<dt><code>GC_FOR_MALLOC</code></dt>
+<dd>A garbage collection caused because your app attempted to allocate memory when your heap was
+already full, so the system had to stop your app and reclaim memory.</dd>
+
+<dt><code>GC_HPROF_DUMP_HEAP</code></dt>
+<dd>A garbage collection that occurs when you create an HPROF file to analyze your heap.</dd>
+
+<dt><code>GC_EXPLICIT</code>
+<dd>An explicit garbage collection, such as when you call {@link java.lang.System#gc()} (which you
+should avoid calling and instead trust the garbage collector to run when needed).</dd>
+
+<dt><code>GC_EXTERNAL_ALLOC</code></dt>
+<dd>This happens only on API level 10 and lower (newer versions allocate everything in the Dalvik
+heap). A garbage collection for externally allocated memory (such as the pixel data stored in
+native memory or NIO byte buffers).</dd>
+</dl>
+</dd>
+
+<dt>Amount freed</dt>
+<dd>The amount of memory reclaimed from this garbage collection.</dd>
+
+<dt>Heap stats</dt>
+<dd>Percentage free and (number of live objects)/(total heap size).</dd>
+
+<dt>External memory stats</dt>
+<dd>Externally allocated memory on API level 10 and lower (amount of allocated memory) / (limit at
+which collection will occur).</dd>
+
+<dt>Pause time</dt>
+<dd>Larger heaps will have larger pause times. Concurrent pause times show two pauses: one at the
+beginning of the collection and another near the end.</dd>
+</dl>
+
+<p>For example:</p>
+
+<pre class="no-pretty-print">
+D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
+</pre>
+
+<p>As these log messages stack up, look out for increases in the heap stats (the
+{@code 3571K/9991K} value in the above example). If this value
+continues to increase and doesn't ever seem to get smaller, you could have a memory leak.</p>
+
+
+<h2 id="ViewHeap">Viewing Heap Updates</h2>
+
+<p>To get a little information about what kind of memory your app is using and when, you can view
+real-time updates to your app's heap in the Device Monitor:</p>
+
+<ol>
+<li>Open the Device Monitor.
+<p>From your <code>&lt;sdk>/tools/</code> directory, launch the <code>monitor</code> tool.</p>
+</li>
+<li>In the Debug Monitor window, select your app's process from the list on the left.</li>
+<li>Click <strong>Update Heap</strong> above the process list.</li>
+<li>In the right-side panel, select the <strong>Heap</strong> tab.</li>
+</ol>
+
+<p>The Heap view shows some basic stats about your heap memory usage, updated after every
+garbage collection. To see the first update, click the <strong>Cause GC</strong> button.</p>
+
+<img src="{@docRoot}images/tools/monitor-vmheap@2x.png" width="760" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> The Device Monitor tool,
+showing the <strong>[1] Update Heap</strong> and <strong>[2] Cause GC</strong> buttons.
+The Heap tab on the right shows the heap results.</p>
+
+<p>Continue interacting with your app to watch your heap allocation update with each garbage
+collection. This can help you identify which actions in your app are likely causing too much
+allocation and where you should try to reduce allocations and release
+resources.</p>
+
+
+
+<h2 id="TrackAllocations">Tracking Allocations</h2>
+
+<p>As you start narrowing down memory issues, you should also use the Allocation Tracker to
+get a better understanding of where your memory-hogging objects are allocated. The Allocation
+Tracker can be useful not only for looking at specific uses of memory, but also to analyze critical
+code paths in an app such as scrolling.</p>
+
+<p>For example, tracking allocations when flinging a list in your app allows you to see all the
+allocations that need to be done for that behavior, what thread they are on, and where they came
+from. This is extremely valuable for tightening up these paths to reduce the work they need and
+improve the overall smoothness of the UI.</p>
+
+<p>To use Allocation Tracker:</p>
+<ol>
+<li>Open the Device Monitor.
+<p>From your <code>&lt;sdk>/tools/</code> directory, launch the <code>monitor</code> tool.</p>
+</li>
+<li>In the DDMS window, select your app's process in the left-side panel.</li>
+<li>In the right-side panel, select the <strong>Allocation Tracker</strong> tab.</li>
+<li>Click <strong>Start Tracking</strong>.</li>
+<li>Interact with your app to execute the code paths you want to analyze.</li>
+<li>Click <strong>Get Allocations</strong> every time you want to update the
+list of allocations.</li>
+ </ol>
+
+<p>The list shows all recent allocations,
+currently limited by a 512-entry ring buffer. Click on a line to see the stack trace that led to
+the allocation. The trace shows you not only what type of object was allocated, but also in which
+thread, in which class, in which file and at which line.</p>
+
+<img src="{@docRoot}images/tools/monitor-tracker@2x.png" width="760" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> The Device Monitor tool,
+showing recent app allocations and stack traces in the Allocation Tracker.</p>
+
+
+<p class="note"><strong>Note:</strong> You will always see some allocations from {@code
+DdmVmInternal} and else where that come from the allocation tracker itself.</p>
+
+<p>Although it's not necessary (nor possible) to remove all allocations for your performance
+critical code paths, the allocation tracker can help you identify important issues in your code.
+For instance, some apps might create a new {@link android.graphics.Paint} object on every draw.
+Moving that object into a global member is a simple fix that helps improve performance.</p>
+
+
+
+
+
+
+<h2 id="ViewingAllocations">Viewing Overall Memory Allocations</h2>
+
+<p>For further analysis, you may want to observe how that your app's memory is
+divided between different categories, which you can do with the <code>adb meminfo</code> data.</p>
+
+<p>When talking about how much RAM your app is using with this data, the key metrics
+discussed below are:</p>
+
+<dl>
+<dt>Private (Clean and Dirty) RAM</dt>
+<dd>This is memory that is being used by only your process. This is the bulk of the RAM that the system
+can reclaim when your app’s process is destroyed. Generally, the most important portion of this is
+“private dirty” RAM, which is the most expensive because it is used by only your process and its
+contents exist only in RAM so can’t be paged to storage (because Android does not use swap). All
+Dalvik and native heap allocations you make will be private dirty RAM; Dalvik and native
+allocations you share with the Zygote process are shared dirty RAM.</dd>
+
+<dt>Proportional Set Size (PSS)</dt>
+<dd>This is a measurement of your app’s RAM use that takes into account sharing pages across processes.
+Any RAM pages that are unique to your process directly contribute to its PSS value, while pages
+that are shared with other processes contribute to the PSS value only in proportion to the amount
+of sharing. For example, a page that is shared between two processes will contribute half of its
+size to the PSS of each process.</dd>
+</dl>
+
+
+<p>A nice characteristic of the PSS measurement is that you can add up the PSS across all processes to
+determine the actual memory being used by all processes. This means PSS is a good measure for the
+actual RAM weight of a process and for comparison against the RAM use of other processes and the
+total available RAM.</p>
+
+<p>You can look at the memory use of your app (measured in kilobytes) with the
+following adb command:</p>
+
+<pre class="no-pretty-print">
+adb shell dumpsys meminfo &lt;package_name>
+</pre>
+
+<p>For example, below is the the output for Gmail’s process on a tablet device. There is a lot of
+information here, but key points for discussion are highlighted in different colors.</p>
+
+<p class="note"><strong>Note:</strong> The information you see may vary slightly from what is shown
+here, as some details of the output differ across platform versions.</p>
+
+<pre class="no-pretty-print">
+** MEMINFO in pid 9953 [com.google.android.gm] **
+                 Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
+               Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
+              ------  ------  ------  ------  ------  ------  ------  ------  ------
+  Native Heap      0       0       0       0       0       0    7800    7637(6)  126
+  Dalvik Heap   5110(3)    0    4136    4988(3)    0       0    9168    8958(6)  210
+ Dalvik Other   2850       0    2684    2772       0       0
+        Stack     36       0       8      36       0       0
+       Cursor    136       0       0     136       0       0
+       Ashmem     12       0      28       0       0       0
+    Other dev    380       0      24     376       0       4
+     .so mmap   5443(5) 1996    2584    2664(5) 5788    1996(5)
+    .apk mmap    235      32       0       0    1252      32
+    .ttf mmap     36      12       0       0      88      12
+    .dex mmap   3019(5) 2148       0       0    8936    2148(5)
+   Other mmap    107       0       8       8     324      68
+      Unknown   6994(4)    0     252    6992(4)    0       0
+        TOTAL  24358(1) 4188    9724   17972(2)16388    4260(2)16968   16595     336
+ 
+ Objects
+               Views:    426         ViewRootImpl:        3(8)
+         AppContexts:      6(7)        Activities:        2(7)
+              Assets:      2        AssetManagers:        2
+       Local Binders:     64        Proxy Binders:       34
+    Death Recipients:      0
+     OpenSSL Sockets:      1
+ 
+ SQL
+         MEMORY_USED:   1739
+  PAGECACHE_OVERFLOW:   1164          MALLOC_SIZE:       62
+</pre>
+
+<p>Generally, you should be concerned with only the <code>Pss Total</code> and <code>Private Dirty</code>
+columns. In some cases, the <code>Private Clean</code> and <code>Heap Alloc</code> columns also offer
+interesting data. Here is some more information about the different memory allocations (the rows)
+you should observe:
+
+<dl>
+<dt><code>Dalvik Heap</code></dt>
+<dd>The RAM used by Dalvik allocations in your app. The <code>Pss Total</code> includes all Zygote
+allocations (weighted by their sharing across processes, as described in the PSS definition above).
+The <code>Private Dirty</code> number is the actual RAM committed to only your app’s heap, composed of
+your own allocations and any Zygote allocation pages that have been modified since forking your
+app’s process from Zygote.
+
+<p class="note"><strong>Note:</strong> On newer platform versions that have the <code>Dalvik
+Other</code> section, the <code>Pss Total</code> and <code>Private Dirty</code> numbers for Dalvik Heap do
+not include Dalvik overhead such as the just-in-time compilation (JIT) and garbage collection (GC)
+bookkeeping, whereas older versions list it all combined under <code>Dalvik</code>.</p>
+
+<p>The <code>Heap Alloc</code> is the amount of memory that the Dalvik and native heap allocators keep
+track of for your app. This value is larger than <code>Pss Total</code> and <code>Private Dirty</code>
+because your process was forked from Zygote and it includes allocations that your process shares
+with all the others.</p>
+</dd>
+
+<dt><code>.so mmap</code> and <code>.dex mmap</code></dt>
+<dd>The RAM being used for mmapped <code>.so</code> (native) and <code>.dex</code> (Dalvik) code. The
+<code>Pss Total</code> number includes platform code shared across apps; the <code>Private Clean</code> is
+your app’s own code. Generally, the actual mapped size will be much larger—the RAM here is only
+what currently needs to be in RAM for code that has been executed by the app. However, the .so mmap
+has a large private dirty, which is due to fix-ups to the native code when it was loaded into its
+final address.
+</dd>
+
+<dt><code>Unknown</code></dt>
+<dd>Any RAM pages that the system could not classify into one of the other more specific items.
+Currently, this contains mostly native allocations, which cannot be identified by the tool when
+collecting this data due to Address Space Layout Randomization (ASLR). As with the Dalvik heap, the
+<code>Pss Total</code> for Unknown takes into account sharing with Zygote, and <code>Private Dirty</code>
+is unknown RAM dedicated to only your app.
+</dd>
+
+<dt><code>TOTAL</code></dt>
+<dd>The total Proportional Set Size (PSS) RAM used by your process. This is the sum of all PSS fields
+above it. It indicates the overall memory weight of your process, which can be directly compared
+with other processes and the total available RAM.
+
+<p>The <code>Private Dirty</code> and <code>Private Clean</code> are the total allocations within your
+process, which are not shared with other processes. Together (especially <code>Private Dirty</code>),
+this is the amount of RAM that will be released back to the system when your process is destroyed.
+Dirty RAM is pages that have been modified and so must stay committed to RAM (because there is no
+swap); clean RAM is pages that have been mapped from a persistent file (such as code being
+executed) and so can be paged out if not used for a while.</p>
+
+</dd>
+
+<dt><code>ViewRootImpl</code></dt>
+<dd>The number of root views that are active in your process. Each root view is associated with a
+window, so this can help you identify memory leaks involving dialogs or other windows.
+</dd>
+
+<dt><code>AppContexts</code> and <code>Activities</code></dt>
+<dd>The number of app {@link android.content.Context} and {@link android.app.Activity} objects that
+currently live in your process. This can be useful to quickly identify leaked {@link
+android.app.Activity} objects that can’t be garbage collected due to static references on them,
+which is common. These objects often have a lot of other allocations associated with them and so
+are a good way to track large memory leaks.</dd>
+
+<p class="note"><strong>Note:</strong> A {@link android.view.View} or {@link
+android.graphics.drawable.Drawable} object also holds a reference to the {@link
+android.app.Activity} that it's from, so holding a {@link android.view.View} or {@link
+android.graphics.drawable.Drawable} object can also lead to your app leaking an {@link
+android.app.Activity}.</p>
+
+</dd>
+</dl>
+
+
+
+
+
+
+
+
+
+<h2 id="HeapDump">Capturing a Heap Dump</h2>
+
+<p>A heap dump is a snapshot of all the objects in your app's heap, stored in a binary format called
+HPROF. Your app's heap dump provides information about the overall state of your app's heap so you
+can track down problems you might have identified while viewing heap updates.</p>
+
+<p>To retrieve your heap dump:</p>
+<ol>
+<li>Open the Device Monitor.
+<p>From your <code>&lt;sdk>/tools/</code> directory, launch the <code>monitor</code> tool.</p>
+</li>
+<li>In the DDMS window, select your app's process in the left-side panel.</li>
+<li>Click <strong>Dump HPROF file</strong>, shown in figure 3.</li>
+<li>In the window that appears, name your HPROF file, select the save location,
+then click <strong>Save</strong>.</li>
+</ol>
+
+<img src="{@docRoot}images/tools/monitor-hprof@2x.png" width="760" alt="" />
+<p class="img-caption"><strong>Figure 3.</strong> The Device Monitor tool,
+showing the <strong>[1] Dump HPROF file</strong> button.</p>
+
+<p>If you need to be more precise about when the dump is created, you can also create a heap dump
+at the critical point in your app code by calling {@link android.os.Debug#dumpHprofData
+dumpHprofData()}.</p>
+
+<p>The heap dump is provided in a format that's similar to, but not identical to one from the Java
+HPROF tool. The major difference in an Android heap dump is due to the fact that there are a large
+number of allocations in the Zygote process. But because the Zygote allocations are shared across
+all app processes, they don’t matter very much to your own heap analysis.</p>
+
+<p>To analyze your heap dump, you can use a standard tool like jhat or the <a href=
+"http://www.eclipse.org/mat/downloads.php">Eclipse Memory Analyzer Tool</a> (MAT). However, first
+you'll need to convert the HPROF file from Android's format to the J2SE HPROF format. You can do
+this using the <code>hprof-conv</code> tool provided in the <code>&lt;sdk&gt;/tools/</code>
+directory. Simply run the <code>hprof-conv</code> command with two arguments: the original HPROF
+file and the location to write the converted HPROF file. For example:</p>
+
+<pre class="no-pretty-print">
+hprof-conv heap-original.hprof heap-converted.hprof
+</pre>
+
+<p class="note"><strong>Note:</strong> If you're using the version of DDMS that's integrated into
+Eclipse, you do not need to perform the HPROF converstion—it performs the conversion by
+default.</p>
+
+<p>You can now load the converted file in MAT or another heap analysis tool that understands
+the J2SE HPROF format.</p>
+
+<p>When analyzing your heap, you should look for memory leaks caused by:</p>
+<ul>
+<li>Long-lived references to an Activity, Context, View, Drawable, and other objects that may hold a
+reference to the container Activity or Context.</li>
+<li>Non-static inner classes (such as a Runnable, which can hold the Activity instance).</li>
+<li>Caches that hold objects longer than necessary.</li>
+</ul>
+
+
+<h3 id="EclipseMat">Using the Eclipse Memory Analyzer Tool</h3>
+
+<p>The <a href=
+"http://www.eclipse.org/mat/downloads.php">Eclipse Memory Analyzer Tool</a> (MAT) is just one
+tool that you can use to analyze your heap dump. It's also quite powerful so most of its
+capabilities are beyond the scope of this document, but here are a few tips to get you started.
+
+<p>Once you open your converted HPROF file in MAT, you'll see a pie chart in the Overview,
+showing what your largest objects are. Below this chart, are links to couple of useful features:</p>
+
+<ul>
+  <li>The <strong>Histogram view</strong> shows a list of all classes and how many instances
+  there are of each.
+  <p>You might want to use this view to find extra instances of classes for which you know there
+  should be only a certain number. For example, a common source of leaks is additional instance of
+  your {@link android.app.Activity} class, for which you should usually have only one instance
+  at a time. To find a specific class instance, type the class name into the <em>&lt;Regex></em>
+  field at the top of the list.
+  <p>When you find a class with too many instances, right-click it and select
+  <strong>List objects</strong> &gt; <strong>with incoming references</strong>. In the list that
+  appears, you can determine where an instance is retained by right-clicking it and selecting
+  <strong>Path To GC Roots</strong> &gt; <strong>exclude weak references</strong>.</p>
+  </li>
+
+  <li>The <strong>Dominator tree</strong> shows a list of objects organized by the amount
+  of retained heap.
+  <p>What you should look for is anything that's retaining a portion of heap that's roughly
+  equivalent to the memory size you observed leaking from the <a href="#LogMessages">GC logs</a>,
+  <a href="#ViewHeap">heap updates</a>, or <a href="#TrackAllocations">allocation
+  tracker</a>.
+  <p>When you see something suspicious, right-click on the item and select
+  <strong>Path To GC Roots</strong> &gt; <strong>exclude weak references</strong>. This opens a
+  new tab that traces the references to that object which is causing the alleged leak.</p>
+
+  <p class="note"><strong>Note:</strong> Most apps will show an instance of
+  {@link android.content.res.Resources} near the top with a good chunk of heap, but this is
+  usually expected when your app uses lots of resources from your {@code res/} directory.</p>
+  </li>
+</ul>
+
+
+<img src="{@docRoot}images/tools/mat-histogram@2x.png" width="760" alt="" />
+<p class="img-caption"><strong>Figure 4.</strong> The Eclipse Memory Analyzer Tool (MAT),
+showing the Histogram view and a search for "MainActivity".</p>
+
+<p>For more information about MAT, watch the Google I/O 2011 presentation,
+<a href="http://www.youtube.com/watch?v=_CruQY55HOk">Memory management for Android apps</a>,
+which includes a walkthrough using MAT beginning at about <a href=
+"http://www.youtube.com/watch?v=_CruQY55HOk&amp;feature=player_detailpage#t=1270">21:10</a>.
+Also refer to the <a href="http://wiki.eclipse.org/index.php/MemoryAnalyzer">Eclipse Memory
+Analyzer documentation</a>.</p>
+
+<h4 id="MatCompare">Comparing heap dumps</h4>
+
+<p>You may find it useful to compare your app's heap state at two different points in time in order
+to inspect the changes in memory allocation. To compare two heap dumps using MAT:</p>
+
+<ol>
+  <li>Create two HPROF files as described above, in <a href="#HeapDump">Capturing a Heap Dump</a>.
+  <li>Open the first HPROF file in MAT (<strong>File</strong> > <strong>Open Heap Dump</strong>).
+  <li>In the Navigation History view (if not visible, select <strong>Window</strong> >
+  <strong>Navigation History</strong>), right-click on <strong>Histogram</strong> and select
+  <strong>Add to Compare Basket</strong>.
+  <li>Open the second HPROF file and repeat steps 2 and 3.
+  <li>Switch to the <em>Compare Basket</em> view and click <strong>Compare the Results</strong>
+  (the red "!" icon in the top-right corner of the view).
+</ol>
+
+
+
+
+
+
+<h2 id="TriggerLeaks">Triggering Memory Leaks</h2>
+
+<p>While using the tools described above, you should aggressively stress your app code and try
+forcing memory leaks. One way to provoke memory leaks in your app is to let it
+run for a while before inspecting the heap. Leaks will trickle up to the top of the allocations in
+the heap. However, the smaller the leak, the longer you need to run the app in order to see it.</p>
+
+<p>You can also trigger a memory leak in one of the following ways:</p>
+<ol>
+<li>Rotate the device from portrait to landscape and back again multiple times while in different
+activity states. Rotating the device can often cause an app to leak an {@link android.app.Activity},
+{@link android.content.Context}, or {@link android.view.View} object because the system
+recreates the {@link android.app.Activity} and if your app holds a reference
+to one of those objects somewhere else, the system can't garbage collect it.</li>
+<li>Switch between your app and another app while in different activity states (navigate to
+the Home screen, then return to your app).</li>
+</ol>
+
+<p class="note"><strong>Tip:</strong> You can also perform the above steps by using the "monkey"
+test framework. For more information on running the monkey test framework, read the <a href=
+"{@docRoot}tools/help/monkeyrunner_concepts.html">monkeyrunner</a>
+documentation.</p>
\ No newline at end of file
diff --git a/docs/html/tools/help/monitor.jd b/docs/html/tools/help/monitor.jd
index 18fb49a..e1fe772 100644
--- a/docs/html/tools/help/monitor.jd
+++ b/docs/html/tools/help/monitor.jd
@@ -1,6 +1,18 @@
 page.title=Device Monitor
 @jd:body
 
+  <div id="qv-wrapper">
+    <div id="qv">
+      <h2>See also</h2>
+
+      <ol>
+        <li><a href="{@docRoot}tools/debugging/debugging-memory.html"
+          >Investigating Your RAM Usage</a></li>
+      </ol>
+    </div>
+  </div>
+
+
 <p>Android Device Monitor is a stand-alone tool that provides a graphical user interface for
 several Android application debugging and analysis tools. The Monitor tool does not
 require installation of a integrated development environment, such as Eclipse, and encapsulates the
@@ -14,6 +26,7 @@
   <li>Pixel Perfect magnification viewer</li>
 </ul>
 
+
 <h2 id="usage">Usage</h2>
 
 <p>To start Device Monitor, enter the following command from the SDK <code>tools/</code>
@@ -22,3 +35,7 @@
 
 <p>Start an Android emulator or connect an Android device via USB cable, and connect Device
 Monitor to the device by selecting it in the <strong>Devices</strong> window.</p>
+
+<p class="note"><strong>Note:</strong> Only one debugger can be connected to your device at a time.
+If you're using ADT, you may need to close the debugging tool before launching the Device Monitor
+in order for the device to be fully debuggable.</p>
diff --git a/docs/html/tools/tools_toc.cs b/docs/html/tools/tools_toc.cs
index 8ad61ec..39eecf8 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -139,6 +139,7 @@
       <li><a href="<?cs var:toroot ?>tools/debugging/debugging-ui.html"><span class="en">Optimizing your UI</span></a></li>
       <li><a href="<?cs var:toroot ?>tools/debugging/debugging-tracing.html"><span class="en">Profiling with Traceview and dmtracedump</span></a></li>
       <li><a href="<?cs var:toroot ?>tools/debugging/systrace.html"><span class="en">Analysing Display and Performance with Systrace</span></a></li>
+      <li><a href="<?cs var:toroot ?>tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a></li>
       <li><a href="<?cs var:toroot ?>tools/debugging/debugging-devtools.html"><span class="en">Using the Dev Tools App</span></a></li>
     </ul>
   </li>
diff --git a/docs/html/training/animation/index.jd b/docs/html/training/animation/index.jd
index b2815fc..b6940f8 100644
--- a/docs/html/training/animation/index.jd
+++ b/docs/html/training/animation/index.jd
@@ -1,5 +1,5 @@
 page.title=Adding Animations
-page.tags="animation","views","layout","user interface"
+page.tags="Animator","views","layout","user interface"
 trainingnavtop=true
 startpage=true
 
diff --git a/docs/html/training/articles/memory.jd b/docs/html/training/articles/memory.jd
new file mode 100644
index 0000000..cdc0cd4
--- /dev/null
+++ b/docs/html/training/articles/memory.jd
@@ -0,0 +1,740 @@
+page.title=Managing Your App's Memory
+page.tags="ram","low memory","OutOfMemoryError","onTrimMemory"
+page.article=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>In this document</h2>
+<ol class="nolist">
+  <li><a href="#Android">How Android Manages Memory</a>
+    <ol>
+      <li><a href="#SharingRAM">Sharing Memory</a></li>
+      <li><a href="#AllocatingRAM">Allocating and Reclaiming App Memory</a></li>
+      <li><a href="#RestrictingMemory">Restricting App Memory</a></li>
+      <li><a href="#SwitchingApps">Switching Apps</a></li>
+    </ol>
+  </li>
+  <li><a href="#YourApp">How Your App Should Manage Memory</a>
+    <ol>
+      <li><a href="#Services">Use services sparingly</a></li>
+      <li><a href="#ReleaseMemoryAsUiGone">Release memory when your user interface becomes hidden</a></li>
+      <li><a href="#ReleaseMemoryAsTight">Release memory as memory becomes tight</a></li>
+      <li><a href="#CheckHowMuchMemory">Check how much memory you should use</a></li>
+      <li><a href="#Bitmaps">Avoid wasting memory with bitmaps</a></li>
+      <li><a href="#DataContainers">Use optimized data containers</a></li>
+      <li><a href="#Overhead">Be aware of memory overhead</a></li>
+      <li><a href="#Abstractions">Be careful with code abstractions</a></li>
+      <li><a href="#NanoProto">Use nano protobufs for serialized data</a></li>
+      <li><a href="#DependencyInjection">Avoid dependency injection frameworks</a></li>
+      <li><a href="#ExternalLibs">Be careful about using external libraries</a></li>
+      <li><a href="#OverallPerf">Optimize overall performance</a></li>
+      <li><a href="#Proguard">Use ProGuard to strip out any unneeded code</a></li>
+      <li><a href="#Zipalign">Use zipalign on your final APK</a></li>
+      <li><a href="#AnalyzeRam">Analyze your RAM usage</a></li>
+      <li><a href="#MultipleProcesses">Use multiple processes</a></li>
+    </ol>
+  </li>
+</ol>
+<h2>See Also</h2>
+<ul>
+  <li><a href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>
+  </li>
+</ul>
+
+</div>
+</div>
+
+
+<p>Random-access memory (RAM) is a valuable resource in any software development environment, but
+it's even more valuable on a mobile operating system where physical memory is often constrained.
+Although Android's Dalvik virtual machine performs routine garbage collection, this doesn't allow
+you to ignore when and where your app allocates and releases memory.</p>
+
+<p>In order for the garbage collector to reclaim memory from your app, you need to avoid
+introducing memory leaks (usually caused by holding onto object references in global members) and
+release any {@link java.lang.ref.Reference} objects at the appropriate time (as defined by
+lifecycle callbacks discussed further below). For most apps, the Dalvik garbage collector takes
+care of the rest: the system reclaims your memory allocations when the corresponding objects leave
+the scope of your app's active threads.</p>
+
+<p>This document explains how Android manages app processes and memory allocation, and how you can
+proactively reduce memory usage while developing for Android. For more information about general
+practices to clean up your resources when programming in Java, refer to other books or online
+documentation about managing resource references. If you’re looking for information about how to
+analyze your app’s memory once you’ve already built it, read <a
+href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
+
+
+
+
+<h2 id="Android">How Android Manages Memory</h2>
+
+<p>Android does not offer swap space for memory, but it does use <a href=
+"http://en.wikipedia.org/wiki/Paging" class="external-link">paging</a> and <a href=
+"http://en.wikipedia.org/wiki/Memory-mapped_files" class="external-link">memory-mapping</a>
+(mmapping) to manage memory. This means that any memory you modify&mdash;whether by allocating
+new objects or touching mmapped pages&mdash;remains resident in RAM and cannot be paged out.
+So the only way to completely release memory from your app is to release object references you may
+be holding, making the memory available to the garbage collector. That is with one exception:
+any files mmapped in without modification, such as code, can be paged out of RAM if the system
+wants to use that memory elsewhere.</p>
+
+
+<h3 id="SharingRAM">Sharing Memory</h3>
+
+<p>In order to fit everything it needs in RAM, Android tries to share RAM pages across processes. It
+can do so in the following ways:</p>
+<ul>
+<li>Each app process is forked from an existing process called Zygote.
+The Zygote process starts when the system boots and loads common framework code and resources
+(such as activity themes). To start a new app process, the system forks the Zygote process then
+loads and runs the app's code in the new process. This allows most of the RAM pages allocated for
+framework code and resources to be shared across all app processes.</li>
+
+<li>Most static data is mmapped into a process. This not only allows that same data to be shared
+between processes but also allows it to be paged out when needed. Example static data include:
+Dalvik code (by placing it in a pre-linked {@code .odex} file for direct mmapping), app resources
+(by designing the resource table to be a structure that can be mmapped and by aligning the zip
+entries of the APK), and traditional project elements like native code in {@code .so} files.</li>
+
+<li>In many places, Android shares the same dynamic RAM across processes using explicitly allocated
+shared memory regions (either with ashmem or gralloc). For example, window surfaces use shared
+memory between the app and screen compositor, and cursor buffers use shared memory between the
+content provider and client.</li>
+</ul>
+
+<p>Due to the extensive use of shared memory, determining how much memory your app is using requires
+care. Techniques to properly determine your app's memory use are discussed in <a
+href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
+
+
+<h3 id="AllocatingRAM">Allocating and Reclaiming App Memory</h3>
+
+<p>Here are some facts about how Android allocates then reclaims memory from your app:</p>
+
+<ul>
+<li>The Dalvik heap for each process is constrained to a single virtual memory range. This defines
+the logical heap size, which can grow as it needs to (but only up to a limit that the system defines
+for each app).</li>
+
+<li>The logical size of the heap is not the same as the amount of physical memory used by the heap.
+When inspecting your app's heap, Android computes a value called the Proportional Set Size (PSS),
+which accounts for both dirty and clean pages that are shared with other processes&mdash;but only in an
+amount that's proportional to how many apps share that RAM. This (PSS) total is what the system
+considers to be your physical memory footprint. For more information about PSS, see the <a
+href="{@docRoot}tools/debugging/debugging-memory.html#ViewingAllocations">Investigating Your
+RAM Usage</a> guide.</li>
+
+<li>The Dalvik heap does not compact the logical size of the heap, meaning that Android does not
+defragment the heap to close up space. Android can only shrink the logical heap size when there
+is unused space at the end of the heap. But this doesn't mean the physical memory used by the heap
+can't shrink. After garbage collection, Dalvik walks the heap and finds unused pages, then returns
+those pages to the kernel using madvise. So, paired allocations and deallocations of large
+chunks should result in reclaiming all (or nearly all) the physical memory used. However,
+reclaiming memory from small allocations can be much less efficient because the page used
+for a small allocation may still be shared with something else that has not yet been freed.</li>
+</ul>
+
+
+<h3 id="RestrictingMemory">Restricting App Memory</h3>
+
+<p>To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size
+for each app. The exact heap size limit varies between devices based on how much RAM the device
+has available overall. If your app has reached the heap capacity and tries to allocate more
+memory, it will receive an {@link java.lang.OutOfMemoryError}.</p>
+
+<p>In some cases, you might want to query the system to determine exactly how much heap space you
+have available on the current device&mdash;for example, to determine how much data is safe to keep in a
+cache. You can query the system for this figure by calling {@link
+android.app.ActivityManager#getMemoryClass()}. This returns an integer indicating the number of
+megabytes available for your app's heap. This is discussed further below, under
+<a href="#CheckHowMuchMemory">Check how much memory you should use</a>.</p>
+
+
+<h3 id="SwitchingApps">Switching Apps</h3>
+
+<p>Instead of using swap space when the user switches between apps, Android keeps processes that
+are not hosting a foreground ("user visible") app component in a least-recently used (LRU) cache.
+For example, when the user first launches an app, a process is created for it, but when the user
+leaves the app, that process does <em>not</em> quit. The system keeps the process cached, so if
+the user later returns to the app, the process is reused for faster app switching.</p>
+
+<p>If your app has a cached process and it retains memory that it currently does not need,
+then your app&mdash;even while the user is not using it&mdash;is constraining the system's
+overall performance. So, as the system runs low on memory, it may kill processes in the LRU cache
+beginning with the process least recently used, but also giving some consideration toward
+which processes are most memory intensive. To keep your process cached as long as possible, follow
+the advice in the following sections about when to release your references.</p>
+
+<p>More information about how processes are cached while not running in the foreground and how
+Android decides which ones
+can be killed is available in the <a href="{@docRoot}guide/components/processes-and-threads.html"
+>Processes and Threads</a> guide.</p>
+
+
+
+
+<h2 id="YourApp">How Your App Should Manage Memory</h2>
+
+<p>You should consider RAM constraints throughout all phases of development, including during app
+design (before you begin development). There are many
+ways you can design and write code that lead to more efficient results, through aggregation of the
+same techniques applied over and over.</p>
+
+<p>You should apply the following techniques while designing and implementing your app to make it
+more memory efficient.</p>
+
+
+<h3 id="Services">Use services sparingly</h3>
+
+<p>If your app needs a <a href="{@docRoot}guide/components/services.html">service</a>
+to perform work in the background, do not keep it running unless
+it's actively performing a job. Also be careful to never leak your service by failing to stop it
+when its work is done.</p>
+
+<p>When you start a service, the system prefers to always keep the process for that service
+running. This makes the process very expensive because the RAM used by the service can’t be used by
+anything else or paged out. This reduces the number of cached processes that the system can keep in
+the LRU cache, making app switching less efficient. It can even lead to thrashing in the system
+when memory is tight and the system can’t maintain enough processes to host all the services
+currently running.</p>
+
+<p>The best way to limit the lifespan of your service is to use an {@link
+android.app.IntentService}, which finishes
+itself as soon as it's done handling the intent that started it. For more information, read
+<a href="{@docRoot}training/run-background-service/index.html">Running in a Background Service</a>
+.</p>
+
+<p>Leaving a service running when it’s not needed is <strong>one of the worst memory-management
+mistakes</strong> an Android app can make. So don’t be greedy by keeping a service for your app
+running. Not only will it increase the risk of your app performing poorly due to RAM constraints,
+but users will discover such misbehaving apps and uninstall them.</p>
+
+
+<h3 id="ReleaseMemoryAsUiGone">Release memory when your user interface becomes hidden</h3>
+
+<p>When the user navigates to a different app and your UI is no longer visible, you should
+release any resources that are used by only your UI. Releasing UI resources at this time can
+significantly increase the system's capacity for cached processes, which has a direct impact on the
+quality of the user experience.</p>
+
+<p>To be notified when the user exits your UI, implement the {@link
+android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback in your {@link
+android.app.Activity} classes. You should use this
+method to listen for the {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN} level,
+which indicates your UI is now hidden from view and you should free resources that only your UI
+uses.</p>
+
+
+<p>Notice that your app receives the {@link android.content.ComponentCallbacks2#onTrimMemory
+onTrimMemory()} callback with {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN}
+only when <em>all the UI components</em> of your app process become hidden from the user.
+This is distinct
+from the {@link android.app.Activity#onStop onStop()} callback, which is called when an {@link
+android.app.Activity} instance becomes hidden, which occurs even when the user moves to
+another activity in your app. So although you should implement {@link android.app.Activity#onStop
+onStop()} to release activity resources such as a network connection or to unregister broadcast
+receivers, you usually should not release your UI resources until you receive {@link
+android.content.ComponentCallbacks2#onTrimMemory onTrimMemory(TRIM_MEMORY_UI_HIDDEN)}. This ensures
+that if the user navigates <em>back</em> from another activity in your app, your UI resources are
+still available to resume the activity quickly.</p>
+
+
+
+<h3 id="ReleaseMemoryAsTight">Release memory as memory becomes tight</h3>
+
+<p>During any stage of your app's lifecycle, the {@link
+android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback also tells you when
+the overall device memory is getting low. You should respond by further releasing resources based
+on the following memory levels delivered by {@link android.content.ComponentCallbacks2#onTrimMemory
+onTrimMemory()}:</p>
+
+<ul>
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_MODERATE}
+<p>Your app is running and not considered killable, but the device is running low on memory and the
+system is actively killing processes in the LRU cache.</p>
+</li>
+
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_LOW}
+<p>Your app is running and not considered killable, but the device is running much lower on
+memory so you should release unused resources to improve system performance (which directly
+impacts your app's performance).</p>
+</li>
+
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_RUNNING_CRITICAL}
+<p>Your app is still running, but the system has already killed most of the processes in the
+LRU cache, so you should release all non-critical resources now. If the system cannot reclaim
+sufficient amounts of RAM, it will clear all of the LRU cache and begin killing processes that
+the system prefers to keep alive, such as those hosting a running service.</p>
+</li>
+</ul>
+
+<p>Also, when your app process is currently cached, you may receive one of the following
+levels from {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()}:</p>
+<ul>
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_BACKGROUND}
+<p>The system is running low on memory and your process is near the beginning of the LRU list.
+Although your app process is not at a high risk of being killed, the system may already be killing
+processes in the LRU cache. You should release resources that are easy to recover so your process
+will remain in the list and resume quickly when the user returns to your app.</p>
+</li>
+
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_MODERATE}
+<p>The system is running low on memory and your process is near the middle of the LRU list. If the
+system becomes further constrained for memory, there's a chance your process will be killed.</p>
+</li>
+
+<li>{@link android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE}
+<p>The system is running low on memory and your process is one of the first to be killed if the
+system does not recover memory now. You should release everything that's not critical to
+resuming your app state.</p>
+
+</li>
+</ul>
+
+<p>Because the {@link android.content.ComponentCallbacks2#onTrimMemory onTrimMemory()} callback was
+added in API level 14, you can use the {@link android.content.ComponentCallbacks#onLowMemory()}
+callback as a fallback for older versions, which is roughly equivalent to the {@link
+android.content.ComponentCallbacks2#TRIM_MEMORY_COMPLETE} event.</p>
+
+<p class="note"><strong>Note:</strong> When the system begins killing processes in the LRU cache,
+although it primarily works bottom-up, it does give some consideration to which processes are
+consuming more memory and will thus provide the system more memory gain if killed.
+So the less memory you consume while in the LRU list overall, the better your chances are
+to remain in the list and be able to quickly resume.</p>
+
+
+
+<h3 id="CheckHowMuchMemory">Check how much memory you should use</h3>
+
+<p>As mentioned earlier, each Android-powered device has a different amount of RAM available to the
+system and thus provides a different heap limit for each app. You can call {@link
+android.app.ActivityManager#getMemoryClass()} to get an estimate of your app's available heap in
+megabytes. If your app tries to allocate more memory than is available here, it will receive an
+{@link java.lang.OutOfMemoryError}.</p>
+
+<p>In very special situations, you can request a larger heap size by setting the <a
+href="{@docRoot}guide/topics/manifest/application-element.html#largeHeap">{@code largeHeap}</a>
+attribute to "true" in the manifest <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;application&gt;}</a>
+tag. If you do so, you can call {@link
+android.app.ActivityManager#getLargeMemoryClass()} to get an estimate of the large heap size.</p>
+
+<p>However, the ability to request a large heap is intended only for a small set of apps that can
+justify the need to consume more RAM (such as a large photo editing app). <strong>Never request a
+large heap simply because you've run out of memory</strong> and you need a quick fix&mdash;you
+should use it only when you know exactly where all your memory is being allocated and why it must
+be retained. Yet, even when you're confident your app can justify the large heap, you should avoid
+requesting it to whatever extent possible. Using the extra memory will increasingly be to the
+detriment of the overall user experience because garbage collection will take longer and system
+performance may be slower when task switching or performing other common operations.</p>
+
+<p>Additionally, the large heap size is not the same on all devices and, when running on
+devices that have limited RAM, the large heap size may be exactly the same as the regular heap
+size. So even if you do request the large heap size, you should call {@link
+android.app.ActivityManager#getMemoryClass()} to check the regular heap size and strive to always
+stay below that limit.</p>
+
+
+<h3 id="Bitmaps">Avoid wasting memory with bitmaps</h3>
+
+<p>When you load a bitmap, keep it in RAM only at the resolution you need for the current device's
+screen, scaling it down if the original bitmap is a higher resolution. Keep in mind that an
+increase in bitmap resolution results in a corresponding (increase<sup>2</sup>) in memory needed,
+because both the X and Y dimensions increase.</p>
+
+<p class="note"><strong>Note:</strong> On Android 2.3.x (API level 10) and below, bitmap objects
+always appear as the same size in your app heap regardless of the image resolution (the actual
+pixel data is stored separately in native memory). This makes it more difficult to debug the bitmap
+memory allocation because most heap analysis tools do not see the native allocation. However,
+beginning in Android 3.0 (API level 11), the bitmap pixel data is allocated in your app's Dalvik
+heap, improving garbage collection and debuggability. So if your app uses bitmaps and you're having
+trouble discovering why your app is using some memory on an older device, switch to a device
+running Android 3.0 or higher to debug it.</p>
+
+<p>For more tips about working with bitmaps, read <a
+href="{@docRoot}training/displaying-bitmaps/manage-memory.html">Managing Bitmap Memory</a>.</p>
+
+
+<h3 id="DataContainers">Use optimized data containers</h3>
+
+<p>Take advantage of optimized containers in the Android framework, such as {@link
+android.util.SparseArray}, {@link android.util.SparseBooleanArray}, and {@link
+android.support.v4.util.LongSparseArray}. The generic {@link java.util.HashMap}
+implementation can be quite memory
+inefficient because it needs a separate entry object for every mapping. Additionally, the {@link
+android.util.SparseArray} classes are more efficient because they avoid the system's need
+to <acronym title=
+"Automatic conversion from primitive types to object classes (such as int to Integer)"
+>autobox</acronym>
+the key and sometimes value (which creates yet another object or two per entry). And don't be
+afraid of dropping down to raw arrays when that makes sense.</p>
+
+
+
+<h3 id="Overhead">Be aware of memory overhead</h3>
+
+<p>Be knowledgeable about the cost and overhead of the language and libraries you are using, and
+keep this information in mind when you design your app, from start to finish. Often, things on the
+surface that look innocuous may in fact have a large amount of overhead. Examples include:</p>
+<ul>
+<li>Enums often require more than twice as much memory as static constants. You should strictly
+avoid using enums on Android.</li>
+
+<li>Every class in Java (including anonymous inner classes) uses about 500 bytes of code.</li>
+
+<li>Every class instance has 12-16 bytes of RAM overhead.</li>
+
+<li>Putting a single entry into a {@link java.util.HashMap} requires the allocation of an
+additional entry object that takes 32 bytes (see the previous section about <a
+href="#DataContainers">optimized data containers</a>).</li>
+</ul>
+
+<p>A few bytes here and there quickly add up—app designs that are class- or object-heavy will suffer
+from this overhead. That can leave you in the difficult position of looking at a heap analysis and
+realizing your problem is a lot of small objects using up your RAM.</p>
+
+
+<h3 id="Abstractions">Be careful with code abstractions</h3>
+
+<p>Often, developers use abstractions simply as a "good programming practice," because abstractions
+can improve code flexibility and maintenance. However, abstractions come at a significant cost:
+generally they require a fair amount more code that needs to be executed, requiring more time and
+more RAM for that code to be mapped into memory. So if your abstractions aren't supplying a
+significant benefit, you should avoid them.</p>
+
+
+<h3 id="NanoProto">Use nano protobufs for serialized data</h3>
+
+<p><a href="https://developers.google.com/protocol-buffers/docs/overview">Protocol
+buffers</a> are a language-neutral, platform-neutral, extensible mechanism designed by Google for
+serializing structured data&mdash;think XML, but smaller, faster, and simpler. If you decide to use
+protobufs for your data, you should always use nano protobufs in your client-side code. Regular
+protobufs generate extremely verbose code, which will cause many kinds of problems in your app:
+increased RAM use, significant APK size increase, slower execution, and quickly hitting the DEX
+symbol limit.</p>
+
+<p>For more information, see the "Nano version" section in the <a
+href="https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt"
+class="external-link">protobuf readme</a>.</p>
+
+
+
+<h3 id="DependencyInjection">Avoid dependency injection frameworks</h3>
+
+<p>Using a dependency injection framework such as <a
+href="https://code.google.com/p/google-guice/" class="external-link">Guice</a> or
+<a href="https://github.com/roboguice/roboguice" class="external-link">RoboGuice</a> may be
+attractive because they can simplify the code you write and provide an adaptive environment
+that's useful for testing and other configuration changes. However, these frameworks tend to perform
+a lot of process initialization by scanning your code for annotations, which can require significant
+amounts of your code to be mapped into RAM even though you don't need it. These mapped pages are
+allocated into clean memory so Android can drop them, but that won't happen until the pages have
+been left in memory for a long period of time.</p>
+
+
+<h3 id="ExternalLibs">Be careful about using external libraries</h3>
+
+<p>External library code is often not written for mobile environments and can be inefficient when used
+for work on a mobile client. At the very least, when you decide to use an external library, you
+should assume you are taking on a significant porting and maintenance burden to optimize the
+library for mobile. Plan for that work up-front and analyze the library in terms of code size and
+RAM footprint before deciding to use it at all.</p>
+
+<p>Even libraries supposedly designed for use on Android are potentially dangerous because each
+library may do things differently. For example, one library may use nano protobufs while another
+uses micro protobufs. Now you have two different protobuf implementations in your app. This can and
+will also happen with different implementations of logging, analytics, image loading frameworks,
+caching, and all kinds of other things you don't expect. <a
+href="{@docRoot}tools/help/proguard.html">ProGuard</a> won't save you here because these
+will all be lower-level dependencies that are required by the features for which you want the
+library. This becomes especially problematic when you use an {@link android.app.Activity}
+subclass from a library (which
+will tend to have wide swaths of dependencies), when libraries use reflection (which is common and
+means you need to spend a lot of time manually tweaking ProGuard to get it to work), and so on.</p>
+
+<p>Also be careful not to fall into the trap of using a shared library for one or two features out of
+dozens of other things it does; you don't want to pull in a large amount of code and overhead that
+you don't even use. At the end of the day, if there isn't an existing implementation that is a
+strong match for what you need to do, it may be best if you create your own implementation.</p>
+
+
+<h3 id="OverallPerf">Optimize overall performance</h3>
+
+<p>A variety of information about optimizing your app's overall performance is available
+in other documents listed in <a href="{@docRoot}training/best-performance.html">Best Practices
+for Performance</a>. Many of these documents include optimizations tips for CPU performance, but
+many of these tips also help optimize your app's memory use, such as by reducing the number of
+layout objects required by your UI.</p>
+
+<p>You should also read about <a href="{@docRoot}tools/debugging/debugging-ui.html">optimizing
+your UI</a> with the layout debugging tools and take advantage of
+the optimization suggestions provided by the <a
+href="{@docRoot}tools/debugging/improving-w-lint.html">lint tool</a>.</p>
+
+
+<h3 id="Proguard">Use ProGuard to strip out any unneeded code</h3>
+
+<p>The <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> tool shrinks,
+optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and
+methods with semantically obscure names. Using ProGuard can make your code more compact, requiring
+fewer RAM pages to be mapped.</p>
+
+
+<h3 id="Zipalign">Use zipalign on your final APK</h3>
+
+<p>If you do any post-processing of an APK generated by a build system (including signing it
+with your final production certificate), then you must run <a
+href="{@docRoot}tools/help/zipalign.html">zipalign</a> on it to have it re-aligned.
+Failing to do so can cause your app to require significantly more RAM, because things like
+resources can no longer be mmapped from the APK.</p>
+
+<p class="note"><strong>Note:</strong> Google Play Store does not accept APK files that
+are not zipaligned.</p>
+
+
+<h3 id="AnalyzeRam">Analyze your RAM usage</h3>
+
+<p>Once you achieve a relatively stable build, begin analyzing how much RAM your app is using
+throughout all stages of its lifecycle. For information about how to analyze your app, read <a
+href="{@docRoot}tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p>
+
+
+
+
+<h3 id="MultipleProcesses">Use multiple processes</h3>
+
+<p>If it's appropriate for your app, an advanced technique that may help you manage your app's
+memory is dividing components of your app into multiple processes. This technique must always be
+used carefully and <strong>most apps should not run multiple processes</strong>, as it can easily
+increase&mdash;rather than decrease&mdash;your RAM footprint if done incorrectly. It is primarily
+useful to apps that may run significant work in the background as well as the foreground and can
+manage those operations separately.</p>
+
+
+<p>An example of when multiple processes may be appropriate is when building a music player that
+plays music from a service for long period of time. If
+the entire app runs in one process, then many of the allocations performed for its activity UI must
+be kept around as long as it is playing music, even if the user is currently in another app and the
+service is controlling the playback. An app like this may be split into two process: one for its
+UI, and the other for the work that continues running in the background service.</p>
+
+<p>You can specify a separate process for each app component by declaring the <a href=
+"{@docRoot}guide/topics/manifest/service-element.html#proc">{@code android:process}</a> attribute
+for each component in the manifest file. For example, you can specify that your service should run
+in a process separate from your app's main process by declaring a new process named "background"
+(but you can name the process anything you like):</p>
+
+<pre>
+&lt;service android:name=".PlaybackService"
+         android:process=":background" />
+</pre>
+
+<p>Your process name should begin with a colon (':') to ensure that the process remains private to
+your app.</p>
+
+<p>Before you decide to create a new process, you need to understand the memory implications.
+To illustrate the consequences of each process, consider that an empty process doing basically
+nothing has an extra memory footprint of about 1.4MB, as shown by the memory information
+dump below.</p>
+
+<pre class="no-pretty-print">
+adb shell dumpsys meminfo com.example.android.apis:empty
+
+** MEMINFO in pid 10172 [com.example.android.apis:empty] **
+                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
+              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
+             ------  ------  ------  ------  ------  ------  ------  ------  ------
+  Native Heap     0       0       0       0       0       0    1864    1800      63
+  Dalvik Heap   764       0    5228     316       0       0    5584    5499      85
+ Dalvik Other   619       0    3784     448       0       0
+        Stack    28       0       8      28       0       0
+    Other dev     4       0      12       0       0       4
+     .so mmap   287       0    2840     212     972       0
+    .apk mmap    54       0       0       0     136       0
+    .dex mmap   250     148       0       0    3704     148
+   Other mmap     8       0       8       8      20       0
+      Unknown   403       0     600     380       0       0
+        TOTAL  2417     148   12480    1392    4832     152    7448    7299     148
+</pre>
+
+<p class="note"><strong>Note:</strong> More information about how to read this output is provided
+in <a href="{@docRoot}tools/debugging/debugging-memory.html#ViewingAllocations">Investigating
+Your RAM Usage</a>. The key data here is the <em>Private Dirty</em> and <em>Private
+Clean</em> memory, which shows that this process is using almost 1.4MB of non-pageable RAM
+(distributed across the Dalvik heap, native allocations, book-keeping, and library-loading),
+and another 150K of RAM for code that has been mapped in to execute.</p>
+
+<p>This memory footprint for an empty process is fairly significant and it can quickly
+grow as you start doing work in that process. For
+example, here is the memory use of a process that is created only to show an activity with some
+text in it:</p>
+
+<pre class="no-pretty-print">
+** MEMINFO in pid 10226 [com.example.android.helloactivity] **
+                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
+              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
+             ------  ------  ------  ------  ------  ------  ------  ------  ------
+  Native Heap     0       0       0       0       0       0    3000    2951      48
+  Dalvik Heap  1074       0    4928     776       0       0    5744    5658      86
+ Dalvik Other   802       0    3612     664       0       0
+        Stack    28       0       8      28       0       0
+       Ashmem     6       0      16       0       0       0
+    Other dev   108       0      24     104       0       4
+     .so mmap  2166       0    2824    1828    3756       0
+    .apk mmap    48       0       0       0     632       0
+    .ttf mmap     3       0       0       0      24       0
+    .dex mmap   292       4       0       0    5672       4
+   Other mmap    10       0       8       8      68       0
+      Unknown   632       0     412     624       0       0
+        TOTAL  5169       4   11832    4032   10152       8    8744    8609     134
+</pre>
+
+<p>The process has now almost tripled in size, to 4MB, simply by showing some text in the UI. This
+leads to an important conclusion: If you are going to split your app into multiple processes, only
+one process should be responsible for UI. Other processes should avoid any UI, as this will quickly
+increase the RAM required by the process (especially once you start loading bitmap assets and other
+resources). It may then be hard or impossible to reduce the memory usage once the UI is drawn.</p>
+
+<p>Additionally, when running more than one process, it's more important than ever that you keep your
+code as lean as possible, because any unnecessary RAM overhead for common implementations are now
+replicated in each process. For example, if you are using enums (though <a
+href="#Overhead">you should not use enums</a>), all of
+the RAM needed to create and initialize those constants is duplicated in each process, and any
+abstractions you have with adapters and temporaries or other overhead will likewise be replicated.</p>
+
+<p>Another concern with multiple processes is the dependencies that exist between them. For example,
+if your app has a content provider that you have running in the default process which also hosts
+your UI, then code in a background process that uses that content provider will also require that
+your UI process remain in RAM. If your goal is to have a background process that can run
+independently of a heavy-weight UI process, it can't have dependencies on content providers or
+services that execute in the UI process.</p>
+
+
+
+
+
+
+
+
+
+
+<!-- THE FOLLOWING IS OVERWHELMING AND NOT NECESSARY FOR MOST APPS, LEAVING OUT FOR NOW
+
+
+<p>You can examine the dependencies between your processes with the command:</p>
+
+<pre class="no-pretty-print">
+adb shell dumpsys activity
+</pre>
+
+<p>This dumps various information about the Activity Manager's state, ending with a list of all
+processes in their memory management order, including the reason each process is at its given
+level. For example, below is a dump with the Music app in the foreground.</p>
+
+<pre class="no-pretty-print">
+ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
+  Process LRU list (sorted by oom_adj):
+    PERS # 4: adj=sys  /F  trm= 0 20674:system/1000 (fixed)
+    PERS #39: adj=pers /F  trm= 0 20964:com.android.nfc/1027 (fixed)
+    PERS # 2: adj=pers /F  trm= 0 20959:com.android.phone/1001 (fixed)
+    PERS # 1: adj=pers /F  trm= 0 20779:com.android.systemui/u0a10057 (fixed)
+    Proc #11: adj=fore /FA trm= 0 8663:com.google.android.music:ui/u0a10043 (top-activity)
+    Proc #10: adj=fore /F  trm= 0 30881:com.google.android.music:main/u0a10043 (provider)
+        com.google.android.music/.store.MusicContentProvider<=Proc{8663:com.google.android.music:ui/u0a10043}
+    Proc # 6: adj=fore /F  trm= 0 21014:com.google.process.gapps/u0a10023 (provider)
+        com.google.android.gsf/.settings.GoogleSettingsProvider<=Proc{20935:com.google.process.location/u0a10023}
+    Proc #38: adj=vis  /F  trm= 0 21028:com.android.nfc:handover/1027 (service)
+        com.android.nfc/.handover.HandoverService<=Proc{20964:com.android.nfc/1027}
+    Proc # 7: adj=vis  /B  trm= 0 20935:com.google.process.location/u0a10023 (service)
+        com.google.android.location/.GeocodeService<=Proc{20674:system/1000}
+    Proc # 3: adj=vis  /F  trm= 0 21225:com.android.bluetooth/1002 (service)
+        com.android.bluetooth/.hfp.HeadsetService<=Proc{20674:system/1000}
+    Proc # 0: adj=vis  /F  trm= 0 20908:com.google.android.inputmethod.latin/u0a10035 (service)
+        com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME<=Proc{20674:system/1000}
+    Proc #34: adj=svc  /B  trm= 0 16765:com.google.android.apps.currents/u0a10012 (started-services)
+    Proc #14: adj=svc  /B  trm= 0 21148:com.google.android.gms/u0a10023 (started-services)
+    Proc #12: adj=home /B  trm= 0 20989:com.android.launcher/u0a10036 (home)
+    Proc #37: adj=svcb /B  trm= 0 15194:com.google.android.apps.googlevoice/u0a10089 (started-services)
+    Proc #17: adj=svcb /B  trm= 0 24537:android.process.media/u0a10016 (started-services)
+    Proc #35: adj=bak  /B  trm= 0 16087:com.android.defcontainer/u0a10013 (service)
+        com.android.defcontainer/.DefaultContainerService<=Proc{16050:com.android.settings/1000}
+    Proc #16: adj=bak  /B  trm= 0 7334:com.google.android.gm/u0a10022 (bg-act)
+    Proc #15: adj=bak  /B  trm= 0 22499:com.google.android.googlequicksearchbox/u0a10060 (bg-act)
+    Proc # 9: adj=bak  /B  trm= 0 20856:com.google.android.gsf.login/u0a10023 (bg-empty)
+    Proc #26: adj=bak+1/B  trm= 0 9923:com.android.mms/u0a10042 (bg-act)
+    Proc #23: adj=bak+1/B  trm= 0 16721:com.android.chrome/u0a10010 (bg-act)
+    Proc #22: adj=bak+1/B  trm= 0 17596:com.android.chrome:sandboxed_process0/u0a10010i33 (service)
+        com.android.chrome/org.chromium.content.app.SandboxedProcessService0<=Proc{16721:com.android.chrome/u0a10010}
+    Proc #19: adj=bak+1/B  trm= 0 17442:com.google.android.youtube/u0a10067 (bg-services)
+    Proc #18: adj=bak+2/B  trm= 0 16740:com.google.android.apps.plus/u0a10052 (bg-empty)
+    Proc #13: adj=bak+2/B  trm= 0 7707:com.android.musicfx/u0a10044 (bg-empty)
+    Proc #36: adj=bak+3/B  trm= 0 16050:com.android.settings/1000 (bg-act)
+    Proc #33: adj=bak+3/B  trm= 0 16863:com.android.dialer/u0a10015 (bg-act)
+</pre>
+
+
+<p class="note"><strong>Note:</strong> The exact details of what is shown here will vary across
+platform versions as process management policies are tweaked and improved.</p>
+
+
+<p>Details on the highlighted sections are:</p>
+
+<ol>
+<li>Foreground app: This is the current app running in the foreground -- it is in the "fore" memory
+class because it is the top activity on the activity stack.</li>
+
+<li>Persistent processes: These are processes that are part of the core system that must always be
+running.</li>
+
+<li>Dependent process: This shows how the Music app is using two processes. Its UI process has a
+dependency on the "main" process (through a content provider). So while the UI process is in use,
+the main process must also be kept around. This means the app's memory footprint is actually the
+sum of both processes. You will have this kind of connection on a content provider any time you
+have active calls into it or have unclosed cursors or file streams that came from it.</li>
+
+<li>Visible processes: These are processes that count in some way as "visible" to the user. This
+generally means that it is either something the user can literally see (such as a process hosting a
+paused but visible activity that is behind a non-full-screen dialog) or is something the user might
+notice if the process disappeared (such as a foreground service playing music). You should be
+certain that any process you have running at the "visible" level is indeed critical to the user,
+because they are very expensive to the overall RAM load.</li>
+
+<li>Service processes: These are processes running long-term jobs in a service. This level of the
+list is the start of less-critical processes, which the system has some freedom to kill if RAM is
+needed elsewhere. These services are still quite expensive because they can be killed only
+temporarily and the system tries to keep them running whenever possible.</li>
+
+<li>Home process: A special slot for the process that hosts the current Home activity, to try to
+prevent it from being killed as much as possible. Killing this process is much more damaging to the
+user experience than killing other cached processes, because so much user interaction goes through
+home.</li>
+
+<li>Secondary service processes: These are services that have been running for a relatively long time
+and so should be killed more aggressively when RAM is needed elsewhere.</li>
+
+<li>Cached processes: These are cached processes held in the LRU cache, which allow for fast app
+switching and component launching. These processes are not required and the system will kill them
+as needed to reclaim memory. You will often see a process hosting a running service here—this is
+part of a platform policy of allowing very long-running services to drop down into the LRU list and
+eventually be killed. If the service should continue running (as defined by the {@link
+android.app.Service#onStartCommand onStartCommand()} return value, such as {@link
+android.app.Service#START_STICKY}), the the system eventually restarts it. This avoids issues with
+such services having memory leaks that over time reduce the number of regular cached processes that
+can be kept.</li>
+
+</ol>
+
+<p>This numbered list of processes is essentially the LRU list of processes that the framework
+provides to the kernel to help it determine which processes it should kill as it needs more RAM.
+The kernel's out of memory killer will generally begin from the bottom of this list, killing the
+last process and working its way up. It may not do it in exactly this order, as it can also take
+into consideration other factors such as the relative RAM footprint of processes to some degree.</p>
+
+<p>There are many other options you can use with the activity command to analyze further details of
+your app's state&mdash;use <code>adb shell dumpsys activity -h</code> for help on its use.</p>
+
+-->
diff --git a/docs/html/training/articles/perf-anr.jd b/docs/html/training/articles/perf-anr.jd
index d3b2318..87cfc1c 100644
--- a/docs/html/training/articles/perf-anr.jd
+++ b/docs/html/training/articles/perf-anr.jd
@@ -8,7 +8,7 @@
 <div id="tb">
 
 <h2>In this document</h2>
-<ol>
+<ol class="nolist">
   <li><a href="#anr">What Triggers ANR?</a></li>
   <li><a href="#Avoiding">How to Avoid ANRs</a></li>
   <li><a href="#Reinforcing">Reinforcing Responsiveness</a></li>
diff --git a/docs/html/training/articles/perf-jni.jd b/docs/html/training/articles/perf-jni.jd
index 0d1f04e..9f880ec 100644
--- a/docs/html/training/articles/perf-jni.jd
+++ b/docs/html/training/articles/perf-jni.jd
@@ -8,7 +8,7 @@
 <div id="tb">
 
 <h2>In this document</h2>
-<ol>
+<ol class="nolist">
   <li><a href="#JavaVM_and_JNIEnv">JavaVM and JNIEnv</a></li>
   <li><a href="#threads">Threads</a></li>
   <li><a href="#jclass_jmethodID_and_jfieldID">jclass, jmethodID, and jfieldID</a></li>
diff --git a/docs/html/training/articles/perf-tips.jd b/docs/html/training/articles/perf-tips.jd
index f91ecd7..1660b7f 100644
--- a/docs/html/training/articles/perf-tips.jd
+++ b/docs/html/training/articles/perf-tips.jd
@@ -6,7 +6,7 @@
 <div id="tb">
 
 <h2>In this document</h2>
-<ol>
+<ol class="nolist">
   <li><a href="#ObjectCreation">Avoid Creating Unnecessary Objects</a></li>
   <li><a href="#PreferStatic">Prefer Static Over Virtual</a></li>
   <li><a href="#UseFinal">Use Static Final For Constants</a></li>
@@ -16,7 +16,6 @@
   <li><a href="#AvoidFloat">Avoid Using Floating-Point</a></li>
   <li><a href="#UseLibraries">Know and Use the Libraries</a></li>
   <li><a href="#NativeMethods">Use Native Methods Carefully</a></li>
-  <li><a href="#library">Know And Use The Libraries</a></li>
   <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
   <li><a href="#closing_notes">Closing Notes</a></li>
 </ol>
diff --git a/docs/html/training/articles/security-tips.jd b/docs/html/training/articles/security-tips.jd
index 1ac56b9..54aebac 100644
--- a/docs/html/training/articles/security-tips.jd
+++ b/docs/html/training/articles/security-tips.jd
@@ -553,7 +553,7 @@
 or an explicit intent to a specific application component.</p>
 
 <p>Note that ordered broadcasts can be “consumed” by a recipient, so they
-may not be delivered to all applications.  If you are sending an intent that muse be delivered
+may not be delivered to all applications.  If you are sending an intent that must be delivered
 to a specific receiver, then you must use an explicit intent that declares the receiver
 by nameintent.</p>
 
diff --git a/docs/html/training/basics/fragments/fragment-ui.jd b/docs/html/training/basics/fragments/fragment-ui.jd
index db3119b..14469bf 100644
--- a/docs/html/training/basics/fragments/fragment-ui.jd
+++ b/docs/html/training/basics/fragments/fragment-ui.jd
@@ -122,11 +122,11 @@
                 return;
             }
 
-            // Create an instance of ExampleFragment
+            // Create a new Fragment to be placed in the activity layout
             HeadlinesFragment firstFragment = new HeadlinesFragment();
             
-            // In case this activity was started with special instructions from an Intent,
-            // pass the Intent's extras to the fragment as arguments
+            // In case this activity was started with special instructions from an
+            // Intent, pass the Intent's extras to the fragment as arguments
             firstFragment.setArguments(getIntent().getExtras());
             
             // Add the fragment to the 'fragment_container' FrameLayout
diff --git a/docs/html/training/basics/intents/filters.jd b/docs/html/training/basics/intents/filters.jd
index 0090c98..9b6a111 100644
--- a/docs/html/training/basics/intents/filters.jd
+++ b/docs/html/training/basics/intents/filters.jd
@@ -20,7 +20,8 @@
 
 <h2>You should also read</h2>
 <ul>
-  <li><a href="{@docRoot}training/sharing/index.html">Sharing Content</a></li>
+    <li><a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a></li>
+    <li><a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>
 </ul>
   </div>
 </div>
@@ -152,7 +153,7 @@
 
 <p>For more information about sending and receiving {@link android.content.Intent#ACTION_SEND}
 intents that perform social sharing behaviors, see the lesson about <a
-href="{@docRoot}training/sharing/receive.html">Receiving Content from Other Apps</a>.</p>
+href="{@docRoot}training/sharing/receive.html">Receiving Simple Data from Other Apps</a>.</p>
 
 
 <h2 id="HandleIntent">Handle the Intent in Your Activity</h2>
diff --git a/docs/html/training/basics/intents/index.jd b/docs/html/training/basics/intents/index.jd
index 8876a33..59ba11f 100644
--- a/docs/html/training/basics/intents/index.jd
+++ b/docs/html/training/basics/intents/index.jd
@@ -19,7 +19,8 @@
 
 <h2>You should also read</h2>
 <ul>
-  <li><a href="{@docRoot}training/sharing/index.html">Sharing Content</a></li>
+    <li><a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a></li>
+    <li><a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>
   <li><a
 href="http://android-developers.blogspot.com/2009/11/integrating-application-with-intents.html">
 Integrating Application with Intents (blog post)</a></li>
@@ -49,7 +50,7 @@
 make your app able to respond to intents from other apps.</p>
 
 <h2>Lessons</h2>
- 
+
 <dl>
   <dt><b><a href="sending.html">Sending the User to Another App</a></b></dt>
   <dd>Shows how you can create implicit intents to launch other apps that can perform an
@@ -59,5 +60,5 @@
   <dt><b><a href="filters.html">Allowing Other Apps to Start Your Activity</a></b></dt>
   <dd>Shows how to make activities in your app open for use by other apps by defining
 intent filters that declare the implicit intents your app accepts.</dd>
-</dl> 
+</dl>
 
diff --git a/docs/html/training/basics/intents/result.jd b/docs/html/training/basics/intents/result.jd
index 24ecc46..64fbb8b 100644
--- a/docs/html/training/basics/intents/result.jd
+++ b/docs/html/training/basics/intents/result.jd
@@ -21,7 +21,8 @@
 
 <h2>You should also read</h2>
 <ul>
-  <li><a href="{@docRoot}training/sharing/index.html">Sharing Content</a></li>
+    <li><a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a></li>
+    <li><a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>
 </ul>
 
   </div>
@@ -71,7 +72,7 @@
 
 <h2 id="ReceiveResult">Receive the Result</h2>
 
-<p>When the user is done with the subsequent activity and returns, the system calls your activity's 
+<p>When the user is done with the subsequent activity and returns, the system calls your activity's
 {@link android.app.Activity#onActivityResult onActivityResult()} method. This method includes three
 arguments:</p>
 
diff --git a/docs/html/training/basics/intents/sending.jd b/docs/html/training/basics/intents/sending.jd
index aba3896..79c017b 100644
--- a/docs/html/training/basics/intents/sending.jd
+++ b/docs/html/training/basics/intents/sending.jd
@@ -22,7 +22,7 @@
 
 <h2>You should also read</h2>
 <ul>
-  <li><a href="{@docRoot}training/sharing/index.html">Sharing Content</a></li>
+    <li><a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a></li>
 </ul>
 
   </div>
@@ -200,7 +200,7 @@
 PackageManager packageManager = {@link android.content.Context#getPackageManager()};
 List&lt;ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
 boolean isIntentSafe = activities.size() > 0;
-  
+
 // Start an activity if it's safe
 if (isIntentSafe) {
     startActivity(mapIntent);
diff --git a/docs/html/training/beam-files/index.jd b/docs/html/training/beam-files/index.jd
new file mode 100644
index 0000000..e4bac2e
--- /dev/null
+++ b/docs/html/training/beam-files/index.jd
@@ -0,0 +1,62 @@
+page.title=Sharing Files with NFC
+page.tags="NfcAdapter","Android Beam","share","file transfer"
+
+trainingnavtop=true
+startpage=true
+
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+<ul>
+  <li>Android 4.1 (API Level 16) or higher</li>
+  <li>At least two NFC-enabled Android devices (NFC is not supported in the emulator)</li>
+</ul>
+
+<h2>You should also read</h2>
+<ul>
+  <li>
+    <a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
+    >Using the External Storage</a>
+  </li>
+</ul>
+
+</div>
+</div>
+
+<p>
+    Android allows you to transfer large files between devices using the Android Beam file transfer
+    feature. This feature has a simple API and allows users to start the transfer process by simply
+    touching devices. In response, Android Beam file transfer automatically copies files from one
+    device to the other and notifies the user when it's finished.
+</p>
+<p>
+    While the Android Beam file transfer API handles large amounts of data, the Android Beam NDEF
+    transfer API introduced in Android 4.0 (API level 14) handles small amounts of data such as
+    URIs or other small messages. In addition, Android Beam is only one of the features available
+    in the Android NFC framework, which allows you to read NDEF messages from NFC tags. To learn
+    more about Android Beam, see the topic
+    <a href="{@docRoot}guide/topics/connectivity/nfc/nfc.html#p2p"
+    >Beaming NDEF Messages to Other Devices</a>. To learn more about the NFC framework, see the
+    <a href="{@docRoot}guide/topics/connectivity/nfc/index.html"
+    >Near Field Communication</a> API guide.
+</p>
+<h2>Lessons</h2>
+<dl>
+    <dt>
+        <b><a href="send-files.html">Sending Files to Another Device</a></b>
+    </dt>
+    <dd>Learn how to set up your app to send files to another device.</dd>
+
+    <dt>
+        <b><a href="receive-files.html">Receiving Files from Another Device</a></b>
+    </dt>
+    <dd>
+        Learn how to set up your app to receive files sent by another device.
+    </dd>
+</dl>
+
+
diff --git a/docs/html/training/beam-files/receive-files.jd b/docs/html/training/beam-files/receive-files.jd
new file mode 100644
index 0000000..0613612
--- /dev/null
+++ b/docs/html/training/beam-files/receive-files.jd
@@ -0,0 +1,313 @@
+page.title=Receiving Files from Another Device
+
+trainingnavtop=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+    <li><a href="#IntentFilter">Respond to a Request to Display Data</a></li>
+    <li><a href="#RequestPermissions">Request File Permissions</a></li>
+    <li><a href="#GetFilePath">Get the Directory for Copied Files</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+    <li>
+        <a href="{@docRoot}guide/topics/providers/content-provider-basics.html#ContentURIs"
+        >Content URIs</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Notifications</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
+        >Using the External Storage</a>
+    </li>
+</ul>
+
+</div>
+</div>
+
+<p>
+    Android Beam file transfer copies files to a special directory on the receiving device. It also
+    scans the copied files using the Android Media Scanner and adds entries for media files to
+    the {@link android.provider.MediaStore} provider. This lesson shows you how to respond when the
+    file copy is complete, and how to locate the copied files on the receiving device.
+</p>
+<h2 id="IntentFilter">Respond to a Request to Display Data</h2>
+<p>
+    When Android Beam file transfer finishes copying files to the receiving device, it posts a
+    notification containing an {@link android.content.Intent} with the action
+    {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, the MIME type of the first file that
+    was transferred, and a URI that points to the first file. When the user clicks the notification,
+    this intent is sent out to the system. To have your app respond to this intent, add an
+    <code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"
+    >&lt;intent-filter&gt;</a></code> element for the
+    <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"
+    >&lt;activity&gt;</a></code> element of the {@link android.app.Activity} that should respond.
+    In the <code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"
+    >&lt;intent-filter&gt;</a></code> element, add the following child elements:
+</p>
+<dl>
+    <dt>
+        <code><a href="{@docRoot}guide/topics/manifest/action-element.html"
+        >&lt;action android:name="android.intent.action.VIEW" /&gt;</a></code>
+    </dt>
+    <dd>
+        Matches the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent sent from the
+        notification.
+    </dd>
+    <dt>
+        <code><a href="{@docRoot}guide/topics/manifest/category-element.html"
+        >&lt;category android:name="android.intent.category.CATEGORY_DEFAULT" /&gt;</a></code>
+    </dt>
+    <dd>
+        Matches an {@link android.content.Intent} that doesn't have an explicit category.
+    </dd>
+    <dt>
+        <code><a href="{@docRoot}guide/topics/manifest/data-element.html"
+        >&lt;data android:mimeType="<i>mime-type</i>" /&gt;</a></code>
+    </dt>
+    <dd>
+        Matches a MIME type. Specify only those MIME types that your app can handle.
+    </dd>
+</dl>
+<p>
+    For example, the following snippet shows you how to add an intent filter that
+    triggers the activity <code>com.example.android.nfctransfer.ViewActivity</code>:
+</p>
+<pre>
+    &lt;activity
+        android:name="com.example.android.nfctransfer.ViewActivity"
+        android:label="Android Beam Viewer" &gt;
+        ...
+        &lt;intent-filter&gt;
+            &lt;action android:name="android.intent.action.VIEW"/&gt;
+            &lt;category android:name="android.intent.category.DEFAULT"/&gt;
+            ...
+        &lt;/intent-filter&gt;
+    &lt;/activity&gt;
+</pre>
+<p class="note">
+    <strong>Note:</strong> Android Beam file transfer is not the only source of an
+    {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent. Other apps on the receiving
+    device can also send an {@link android.content.Intent} with this action.
+    Handling this situation is discussed in the section <a href="#GetDirectory"
+    >Get the directory from a content URI</a>.
+</p>
+<h2 id="RequestPermissions">Request File Permissions</h2>
+<p>
+    To read files that Android Beam file transfer copies to the device, request the permission
+    {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}. For example:
+</p>
+<pre>
+    &lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt;</pre>
+<p>
+    If you want to copy transferred files to your app's own storage area, request the permission
+    {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} instead.
+    {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} includes
+    {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}.
+</p>
+<p class="note">
+    <strong>Note:</strong> As of Android 4.2.2 (API level 17), the permission
+    {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE} is
+    only enforced if the user chooses to do so. Future versions of the platform may require this
+    permission in all cases. To ensure forward compatibility, request the permission now, before it
+    becomes required.
+</p>
+<p>
+    Since your app has control over its internal storage area, you don't need to request
+    write permission to copy a transferred file to your internal storage area.
+</p>
+<h2 id="GetFilePath">Get the Directory for Copied Files</h2>
+<p>
+    Android Beam file transfer copies all the files in a single transfer to one directory
+    on the receiving device. The URI in the content {@link android.content.Intent} sent by the
+    Android Beam file transfer notification points to the first transferred file. However, your
+    app may also receive an {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent from a
+    source other than Android Beam file transfer. To determine how you should handle the incoming
+    {@link android.content.Intent}, you need to examine its scheme and authority.
+</p>
+<p>
+    To get the scheme for the URI, call {@link android.net.Uri#getScheme() Uri.getScheme()}. The
+    following code snippet shows you how to determine the scheme and handle the URI accordingly:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    ...
+    // A File object containing the path to the transferred files
+    private File mParentPath;
+    // Incoming Intent
+    private Intent mIntent;
+    ...
+    /*
+     * Called from onNewIntent() for a SINGLE_TOP Activity
+     * or onCreate() for a new Activity. For onNewIntent(),
+     * remember to call setIntent() to store the most
+     * current Intent
+     *
+     */
+    private void handleViewIntent() {
+        ...
+        // Get the Intent action
+        mIntent = getIntent();
+        String action = mIntent.getAction();
+        /*
+         * For ACTION_VIEW, the Activity is being asked to display data.
+         * Get the URI.
+         */
+        if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
+            // Get the URI from the Intent
+            Uri beamUri = mIntent.getData();
+            /*
+             * Test for the type of URI, by getting its scheme value
+             */
+            if (TextUtils.equals(beamUri.getScheme(), "file")) {
+                mParentPath = handleFileUri(beamUri);
+            } else if (TextUtils.equals(
+                    beamUri.getScheme(), "content")) {
+                mParentPath = handleContentUri(beamUri);
+            }
+        }
+        ...
+    }
+    ...
+}
+</pre>
+<h3>Get the directory from a file URI</h3>
+<p>
+    If the incoming {@link android.content.Intent} contains a file URI, the URI contains the
+    absolute file name of a file, including the full directory path and file name. For Android Beam
+    file transfer, the directory path points to the location of the other transferred files, if
+    any. To get the directory path, get the path part of the URI, which contains all of the URI
+    except the <code>file:</code> prefix. Create a {@link java.io.File} from the path part, then
+    get the parent path of the {@link java.io.File}:
+</p>
+<pre>
+    ...
+    public String handleFileUri(Uri beamUri) {
+        // Get the path part of the URI
+        String fileName = beamUri.getPath();
+        // Create a File object for this filename
+        File copiedFile = new File(fileName);
+        // Get a string containing the file's parent directory
+        return copiedFile.getParent();
+    }
+    ...
+</pre>
+
+<h3 id="GetDirectory">Get the directory from a content URI</h3>
+<p>
+    If the incoming {@link android.content.Intent} contains a content URI, the URI may point to a
+    directory and file name stored in the {@link android.provider.MediaStore} content provider. You
+    can detect a content URI for {@link android.provider.MediaStore} by testing the URI's
+    authority value. A content URI for {@link android.provider.MediaStore} may come from
+    Android Beam file transfer or from another app, but in both cases you can retrieve a directory
+    and file name for the content URI.
+</p>
+<p>
+    You can also receive an incoming {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}
+    intent containing a content URI for a content provider other than
+    {@link android.provider.MediaStore}. In this case, the content URI doesn't contain the
+    {@link android.provider.MediaStore} authority value, and the content URI usually doesn't point
+    to a directory.
+</p>
+<p class="note">
+    <strong>Note:</strong> For Android Beam file transfer, you receive a content URI in the
+    {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent if the first incoming file
+    has a MIME type of "audio/*", "image/*", or "video/*", indicating that the file is media-
+    related. Android Beam file transfer indexes the media files it transfers by running Media
+    Scanner on the directory where it stores transferred files. Media Scanner writes its results
+    to the {@link android.provider.MediaStore} content provider, then it passes a content URI
+    for the first file back to Android Beam file transfer. This content URI is the one you
+    receive in the notification {@link android.content.Intent}. To get the directory
+    of the first file, you retrieve it from {@link android.provider.MediaStore} using the content
+    URI.
+</p>
+<h3>Determine the content provider</h3>
+<p>
+    To determine if you can retrieve a file directory from the content URI, determine the
+    the content provider associated with the URI by calling
+    {@link android.net.Uri#getAuthority Uri.getAuthority()} to get the URI's authority. The
+    result has two possible values:
+</p>
+<dl>
+    <dt>
+        {@link android.provider.MediaStore#AUTHORITY MediaStore.AUTHORITY}
+    </dt>
+    <dd>
+        The URI is for a file or files tracked by {@link android.provider.MediaStore}. Retrieve the
+        full file name from {@link android.provider.MediaStore}, and get directory from the file
+        name.
+    </dd>
+    <dt>
+        Any other authority value
+    </dt>
+    <dd>
+        A content URI from another content provider. Display the data associated with the content
+        URI, but don't get the file directory.
+    </dd>
+</dl>
+<p>
+    To get the directory for a {@link android.provider.MediaStore} content URI,
+    run a query that specifies the incoming content URI for the {@link android.net.Uri} argument and
+    the column {@link android.provider.MediaStore.MediaColumns#DATA MediaColumns.DATA} for the
+    projection. The returned {@link android.database.Cursor} contains the full path and name for
+    the file represented by the URI. This path also contains all the other files that Android Beam
+    file transfer just copied to the device.
+</p>
+<p>
+    The following snippet shows you how to test the authority of the content URI and retrieve the
+    the path and file name for the transferred file:
+</p>
+<pre>
+    ...
+    public String handleContentUri(Uri beamUri) {
+        // Position of the filename in the query Cursor
+        int filenameIndex;
+        // File object for the filename
+        File copiedFile;
+        // The filename stored in MediaStore
+        String fileName;
+        // Test the authority of the URI
+        if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
+            /*
+             * Handle content URIs for other content providers
+             */
+        // For a MediaStore content URI
+        } else {
+            // Get the column that contains the file name
+            String[] projection = { MediaStore.MediaColumns.DATA };
+            Cursor pathCursor =
+                    getContentResolver().query(beamUri, projection,
+                    null, null, null);
+            // Check for a valid cursor
+            if (pathCursor != null &amp;&amp;
+                    pathCursor.moveToFirst()) {
+                // Get the column index in the Cursor
+                filenameIndex = pathCursor.getColumnIndex(
+                        MediaStore.MediaColumns.DATA);
+                // Get the full file name including path
+                fileName = pathCursor.getString(filenameIndex);
+                // Create a File object for the filename
+                copiedFile = new File(fileName);
+                // Return the parent directory of the file
+                return new File(copiedFile.getParent());
+             } else {
+                // The query didn't work; return null
+                return null;
+             }
+        }
+    }
+    ...
+</pre>
+<p>
+    To learn more about retrieving data from a content provider, see the section
+    <a href="{@docRoot}guide/topics/providers/content-provider-basics.html#SimpleQuery"
+    >Retrieving Data from the Provider</a>.
+</p>
diff --git a/docs/html/training/beam-files/send-files.jd b/docs/html/training/beam-files/send-files.jd
new file mode 100644
index 0000000..917b87f
--- /dev/null
+++ b/docs/html/training/beam-files/send-files.jd
@@ -0,0 +1,294 @@
+page.title=Sending Files to Another Device
+
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#DeclareFeatures">Declare Features in the Manifest</a>
+  <li><a href="#TestAndroidBeam">Test for Android Beam File Transfer Support</a></li>
+  <li>
+    <a href="#CreateCallback"
+    >Create a Callback Method That Provides Files</a>
+  </li>
+  <li><a href="#ProvideUri">Specify the Files to Send</a>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+    <li><a href="{@docRoot}guide/topics/data/data-storage.html">Storage Options</a></li>
+</ul>
+
+</div>
+</div>
+<p>
+    This lesson shows you how to design your app to send large files to another device using
+    Android Beam file transfer. To send files, you request permission to use NFC and external
+    storage, test to ensure your device supports NFC, and provide URIs to Android Beam file
+    transfer.
+</p>
+<p>
+    The Android Beam file transfer feature has the following requirements:
+</p>
+<ol>
+    <li>
+        Android Beam file transfer for large files is only available in Android 4.1 (API level 16)
+        and higher.
+    </li>
+    <li>
+        Files you want to transfer must reside in external storage. To learn more about using
+        external storage, read <a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
+        >Using the External Storage</a>.
+    </li>
+    <li>
+        Each file you want to transfer must be world-readable. You can set this permission by
+        calling the method {@link java.io.File#setReadable File.setReadable(true,false)}.
+    </li>
+    <li>
+        You must provide a file URI for the files you want to transfer. Android Beam file transfer
+        is unable to handle content URIs generated by
+        {@link android.support.v4.content.FileProvider#getUriForFile FileProvider.getUriForFile}.
+    </li>
+</ol>
+
+<h2 id="DeclareFeatures">Declare Features in the Manifest</h2>
+<p>
+    First, edit your app manifest to declare the permissions and features your app needs.
+</p>
+<h3>Request Permissions</h3>
+<p>
+    To allow your app to use Android Beam file transfer to send files from external storage using
+    NFC, you must request the following permissions in your app manifest:
+</p>
+<dl>
+    <dt>
+        {@link android.Manifest.permission#NFC NFC}
+    </dt>
+    <dd>
+        Allows your app to send data over NFC. To specify this permission, add the following element
+        as a child of the <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
+        >&lt;manifest&gt;</a></code> element:
+<pre>
+    &lt;uses-permission android:name="android.permission.NFC" /&gt;
+</pre>
+    </dd>
+    <dt>
+        {@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}
+    </dt>
+    <dd>
+        Allows your app to read from external storage. To specify this permission, add the following
+        element as a child of the
+        <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
+        >&lt;manifest&gt;</a></code> element:
+<pre>
+    &lt;uses-permission
+            android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt;
+</pre>
+    <p class="note">
+        <strong>Note:</strong> As of Android 4.2.2 (API level 17), this permission is not
+        enforced. Future versions of the platform may require it for apps that want to read from
+        external storage. To ensure forward compatibility, request the permission now, before it
+        becomes required.
+    </p>
+    </dd>
+</dl>
+<h3>Specify the NFC feature</h3>
+<p>
+    Specify that your app uses NFC, by adding a
+    <code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"
+    >&lt;uses-feature&gt;</a></code> element as a child
+    of the <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
+    >&lt;manifest&gt;</a></code> element. Set the <code>android:required</code> attribute to
+    <code>true</code> to indicate that your app won't function unless NFC is present.
+</p>
+<p>
+    The following snippet shows you how to specify the
+    <code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"
+    >&lt;uses-feature&gt;</a></code> element:
+</p>
+<pre>
+&lt;uses-feature
+    android:name="android.hardware.nfc"
+    android:required="true" /&gt;</pre>
+<p>
+    Note that if your app only uses NFC as an option, but still functions if NFC isn't present, you
+    should set <code>android:required</code> to <code>false</code>, and test for NFC in code.
+</p>
+<h3>Specify Android Beam file transfer</h3>
+<p>
+    Since Android Beam file transfer is only available in Android 4.1 (API level 16) and later,
+    if your app depends on Android Beam file transfer for a key part of its functionality you must
+    specify the <code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"
+    >&lt;uses-sdk&gt;</a></code> element with the
+    <code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min"
+    >android:minSdkVersion</a>="16"</code> attribute. Otherwise, you can set
+    <code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min"
+    >android:minSdkVersion</a></code> to another value as necessary, and test for the platform
+    version in code, as described in the following section.
+</p>
+<h2 id="TestAndroidBeam">Test for Android Beam File Transfer Support</h2>
+<p>
+    To specify in your app manifest that NFC is optional, you use the following element:
+</p>
+<pre>
+&lt;uses-feature android:name="android.hardware.nfc" android:required="false" /&gt;</pre>
+<p>
+    If you set the attribute
+    <code><a href="guide/topics/manifest/uses-feature-element.html#required"
+    >android:required</a>="false"</code>, you must test for NFC support and Android Beam file
+    transfer support in code.
+</p>
+<p>
+    To test for Android Beam file transfer support in code, start by testing that the device
+    supports NFC by calling {@link android.content.pm.PackageManager#hasSystemFeature
+    PackageManager.hasSystemFeature()} with the argument
+    {@link android.content.pm.PackageManager#FEATURE_NFC FEATURE_NFC}. Next, check that the Android
+    version supports Android Beam file transfer by testing the value of
+    {@link android.os.Build.VERSION#SDK_INT}. If Android Beam file transfer is supported, get an
+    instance of the NFC controller, which allows you to communicate with the NFC hardware.
+    For example:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    ...
+    NfcAdapter mNfcAdapter;
+    // Flag to indicate that Android Beam is available
+    boolean mAndroidBeamAvailable  = false;
+    ...
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // NFC isn't available on the device
+        if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
+            /*
+             * Disable NFC features here.
+             * For example, disable menu items or buttons that activate
+             * NFC-related features
+             */
+            ...
+        // Android Beam file transfer isn't supported
+        } else if (Build.VERSION.SDK_INT &lt;
+                Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            // If Android Beam isn't available, don't continue.
+            mAndroidBeamAvailable = false;
+            /*
+             * Disable Android Beam file transfer features here.
+             */
+            ...
+        // Android Beam file transfer is available, continue
+        } else {
+        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
+        ...
+        }
+    }
+    ...
+}</pre>
+
+<h2 id="CreateCallback">
+    Create a Callback Method that Provides Files
+</h2>
+<p>
+    Once you've verified that the device supports Android Beam file transfer, add a callback
+    method that the system invokes when Android Beam file transfer detects that the user wants
+    to send files to another NFC-enabled device. In this callback method, return an array of
+    {@link android.net.Uri} objects. Android Beam file transfer copies the files represented by
+    these URIs to the receiving device.
+</p>
+<p>
+    To add the callback method, implement the
+    {@link android.nfc.NfcAdapter.CreateBeamUrisCallback} interface and its method
+    {@link android.nfc.NfcAdapter.CreateBeamUrisCallback#createBeamUris createBeamUris()}. The
+    following snippet shows you how to do this:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    ...
+    // List of URIs to provide to Android Beam
+    private Uri[] mFileUris = new Uri[10];
+    ...
+    /**
+     * Callback that Android Beam file transfer calls to get
+     * files to share
+     */
+    private class FileUriCallback implements
+            NfcAdapter.CreateBeamUrisCallback {
+        public FileUriCallback() {
+        }
+        /**
+         * Create content URIs as needed to share with another device
+         */
+        &#64;Override
+        public Uri[] createBeamUris(NfcEvent event) {
+            return mFileUris;
+        }
+    }
+    ...
+}
+</pre>
+<p>
+    Once you've implemented the interface, provide the callback to Android Beam file transfer by
+    calling {@link android.nfc.NfcAdapter#setBeamPushUrisCallback setBeamPushUrisCallback()}. The
+    following snippet shows you how to do this:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    ...
+    // Instance that returns available files from this app
+    private FileUriCallback mFileUriCallback;
+    ...
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // Android Beam file transfer is available, continue
+        ...
+        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
+        /*
+         * Instantiate a new FileUriCallback to handle requests for
+         * URIs
+         */
+        mFileUriCallback = new FileUriCallback();
+        // Set the dynamic callback for URI requests.
+        mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);
+        ...
+    }
+    ...
+}
+</pre>
+<p class="note">
+    <strong>Note:</strong> You can also provide the array of {@link android.net.Uri} objects
+    directly to the NFC framework through your app's {@link android.nfc.NfcAdapter} instance. Choose
+    this approach if you can define the URIs to transfer before the NFC touch event occurs.
+    To learn more about this approach, see {@link android.nfc.NfcAdapter#setBeamPushUris
+    NfcAdapter.setBeamPushUris()}.
+</p>
+<h2 id="ProvideUri">Specify the Files to Send</h2>
+<p>
+    To transfer one or more files to another NFC-enabled device, get a file URI (a URI with a
+    <code>file</code> scheme) for each file and then add the URI to an array of
+    {@link android.net.Uri} objects. To transfer a file, you must also have permanent read access
+    for the file. For example, the following snippet shows you how to get a file URI from a file
+    name and then add the URI to the array:
+</p>
+<pre>
+        /*
+         * Create a list of URIs, get a File,
+         * and set its permissions
+         */
+        private Uri[] mFileUris = new Uri[10];
+        String transferFile = "transferimage.jpg";
+        File extDir = getExternalFilesDir(null);
+        File requestFile = new File(extDir, transferFile);
+        requestFile.setReadable(true, false);
+        // Get a URI for the File and add it to the list of URIs
+        fileUri = Uri.fromFile(requestFile);
+        if (fileUri != null) {
+            mFileUris[0] = fileUri;
+        } else {
+            Log.e("My Activity", "No File URI available for file.");
+        }
+</pre>
diff --git a/docs/html/training/building-content-sharing.jd b/docs/html/training/building-content-sharing.jd
new file mode 100644
index 0000000..52298c3
--- /dev/null
+++ b/docs/html/training/building-content-sharing.jd
@@ -0,0 +1,8 @@
+page.title=Building Apps with Content Sharing
+page.trainingcourse=true
+
+@jd:body
+
+
+
+<p>These classes teach you how to create apps that share data between apps and devices.</p>
diff --git a/docs/html/training/secure-file-sharing/index.jd b/docs/html/training/secure-file-sharing/index.jd
new file mode 100644
index 0000000..aa009fc
--- /dev/null
+++ b/docs/html/training/secure-file-sharing/index.jd
@@ -0,0 +1,81 @@
+page.title=Sharing Files
+page.tags="FileProvider","share","ContentProvider"
+
+trainingnavtop=true
+startpage=true
+
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+<ul>
+  <li>Android 1.6 (API Level 4) or higher</li>
+  <li>Familiarity with file operations such as opening, reading, and writing files</li>
+</ul>
+
+<h2>You should also read</h2>
+<ul>
+    <li><a href="{@docRoot}guide/topics/data/data-storage.html">Storage Options</a></li>
+    <li><a href="{@docRoot}training/basics/data-storage/files.html">Saving Files</a>
+    <li><a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>
+    Apps often have a need to offer one or more of their files to another app. For example, an image
+    gallery may want to offer files to image editors, or a file management app may want to allow
+    users to copy and paste files between areas in external storage. One way a sending app can
+    share a file is to respond to a request from the receiving app.
+</p>
+<p>
+    In all cases, the only secure way to offer a file from your app to another app is to send the
+    receiving app the file's content URI and grant temporary access permissions to that URI.
+    Content URIs with temporary URI access permissions are secure because they apply only to the
+    app that receives the URI, and they expire automatically. The Android
+    {@link android.support.v4.content.FileProvider} component provides the method
+    {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()} for
+    generating a file's content URI.
+</p>
+<p>
+    If you want to share small amounts of text or numeric data between apps, you should send an
+    {@link android.content.Intent} that contains the data. To learn how to send simple data with an
+    {@link android.content.Intent}, see the training class
+    <a href="{@docRoot}training/sharing/index.html">Sharing Simple Data</a>.
+</p>
+<p>
+    This class explains how to securely share files from your app to another app using content URIs
+    generated by the Android {@link android.support.v4.content.FileProvider} component and
+    temporary permissions that you grant to the receiving app for the content URI.
+</p>
+<h2>Lessons</h2>
+<dl>
+    <dt><b><a href="setup-sharing.html">Setting Up File Sharing</a></b></dt>
+    <dd>
+        Learn how to set up your app to share files.
+    </dd>
+    <dt><b><a href="share-file.html">Sharing a File</a></b></dt>
+    <dd>
+        Learn how to offer a file to another app by generating a content URI for the file,
+        granting access permissions to the URI, and sending the URI to the app.
+    </dd>
+    <dt><b><a href="request-file.html">Requesting a Shared File</a></b></dt>
+    <dd>
+        Learn how to request a file shared by another app, receive the content URI for the file,
+        and use the content URI to open the file.
+    </dd>
+    <dt>
+        <b><a href="retrieve-info.html">Retrieving File Information</a></b>
+    </dt>
+    <dd>
+        Learn how an app can use a content URI generated by a
+        {@link android.support.v4.content.FileProvider} to retrieve file information including
+        MIME type and file size.
+    </dd>
+</dl>
+
+
diff --git a/docs/html/training/secure-file-sharing/request-file.jd b/docs/html/training/secure-file-sharing/request-file.jd
new file mode 100644
index 0000000..116701d
--- /dev/null
+++ b/docs/html/training/secure-file-sharing/request-file.jd
@@ -0,0 +1,147 @@
+page.title=Requesting a Shared File
+
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#SendRequest">Send a Request for the File</a></li>
+  <li><a href="#OpenFile">Access the Requested File</a>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+    <li>
+        <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/providers/content-provider-basics.html#SimpleQuery"
+        >Retrieving Data from the Provider</a>
+    </li>
+</ul>
+
+</div>
+</div>
+
+<p>
+    When an app wants to access a file shared by another app, the requesting app (the client)
+    usually sends a request to the app sharing the files (the server). In most cases, the request
+    starts an {@link android.app.Activity} in the server app that displays the files it can share.
+    The user picks a file, after which the server app returns the file's content URI to the
+    client app.
+</p>
+<p>
+    This lesson shows you how a client app requests a file from a server app, receives the file's
+    content URI from the server app, and opens the file using the content URI.
+</p>
+
+<h2 id="SendRequest">Send a Request for the File</h2>
+<p>
+    To request a file from the server app, the client app calls
+    {@link android.app.Activity#startActivityForResult startActivityForResult} with an
+    {@link android.content.Intent} containing the action such as
+    {@link android.content.Intent#ACTION_PICK ACTION_PICK} and a MIME type that the client app
+    can handle.
+</p>
+<p>
+    For example, the following code snippet demonstrates how to send an
+    {@link android.content.Intent} to a server app in order to start the
+    {@link android.app.Activity} described in <a href="share-file.html#SendURI"
+    >Sharing a File</a>:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    private Intent mRequestFileIntent;
+    private ParcelFileDescriptor mInputPFD;
+    ...
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        mRequestFileIntent = new Intent(Intent.ACTION_PICK);
+        mRequestFileIntent.setType("image/jpg");
+        ...
+    }
+    ...
+    protected void requestFile() {
+        /**
+         * When the user requests a file, send an Intent to the
+         * server app.
+         * files.
+         */
+            startActivityForResult(mRequestFileIntent, 0);
+        ...
+    }
+    ...
+}
+</pre>
+<h2 id="OpenFile">Access the Requested File</h2>
+<p>
+    The server app sends the file's content URI back to the client app in an
+    {@link android.content.Intent}. This {@link android.content.Intent} is passed to the client
+    app in its override of {@link android.app.Activity#onActivityResult onActivityResult()}. Once
+    the client app has the file's content URI, it can access the file by getting its
+    {@link java.io.FileDescriptor}.
+</p>
+<p>
+<p>
+    File security is preserved in this process because the content URI is the only piece of data
+    that the client app receives. Since this URI doesn't contain a directory path, the client app
+    can't discover and open any other files in the server app. Only the client app gets access to
+    the file, and only for the permissions granted by the server app. The permissions are temporary,
+    so once the client app's task stack is finished, the file is no longer accessible outside the
+    server app.
+</p>
+<p>
+    The next snippet demonstrates how the client app handles the
+    {@link android.content.Intent} sent from the server app, and how the client app gets the
+    {@link java.io.FileDescriptor} using the content URI:
+</p>
+<pre>
+    /*
+     * When the Activity of the app that hosts files sets a result and calls
+     * finish(), this method is invoked. The returned Intent contains the
+     * content URI of a selected file. The result code indicates if the
+     * selection worked or not.
+     */
+    &#64;Override
+    public void onActivityResult(int requestCode, int resultCode,
+            Intent returnIntent) {
+        // If the selection didn't work
+        if (resultCode != RESULT_OK) {
+            // Exit without doing anything else
+            return;
+        } else {
+            // Get the file's content URI from the incoming Intent
+            Uri returnUri = returnIntent.getData();
+            /*
+             * Try to open the file for "read" access using the
+             * returned URI. If the file isn't found, write to the
+             * error log and return.
+             */
+            try {
+                /*
+                 * Get the content resolver instance for this context, and use it
+                 * to get a ParcelFileDescriptor for the file.
+                 */
+                mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+                Log.e("MainActivity", "File not found.");
+                return;
+            }
+            // Get a regular file descriptor for the file
+            FileDescriptor fd = mInputPFD.getFileDescriptor();
+            ...
+        }
+    }
+</pre>
+<p>
+    The method {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}
+    returns a {@link android.os.ParcelFileDescriptor} for the file. From this object, the client
+    app gets a {@link java.io.FileDescriptor} object, which it can then use to read the file.
+</p>
diff --git a/docs/html/training/secure-file-sharing/retrieve-info.jd b/docs/html/training/secure-file-sharing/retrieve-info.jd
new file mode 100644
index 0000000..4a2b7d8
--- /dev/null
+++ b/docs/html/training/secure-file-sharing/retrieve-info.jd
@@ -0,0 +1,110 @@
+page.title=Retrieving File Information
+
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#RetrieveMimeType">Retrieve a File's MIME Type</a></li>
+  <li><a href="#RetrieveFileInfo">Retrieve a File's Name and Size</a></li>
+</ol>
+
+<!-- other docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}guide/topics/providers/content-provider-basics.html#SimpleQuery"
+  >Retrieving Data from the Provider</a></li>
+</ul>
+
+</div>
+</div>
+<p>
+    Before a client app tries to work with a file for which it has a content URI, the app can
+    request information about the file from the server app, including the file's data type and
+    file size. The data type helps the client app to determine if it can handle the file, and the
+    file size helps the client app set up buffering and caching for the file.
+</p>
+<p>
+    This lesson demonstrates how to query the server app's
+    {@link android.support.v4.content.FileProvider} to retrieve a file's MIME type and size.
+</p>
+<h2 id="RetrieveMimeType">Retrieve a File's MIME Type</h2>
+<p>
+    A file's data type indicates to the client app how it should handle the file's contents. To get
+    the data type of a shared file given its content URI, the client app calls
+    {@link android.content.ContentResolver#getType ContentResolver.getType()}. This method returns
+    the file's MIME type. By default, a
+    {@link android.support.v4.content.FileProvider} determines the file's MIME type from its
+    filename extension.
+</p>
+<p>
+    The following code snippet demonstrates how a client app retrieves the MIME type of a file once
+    the server app has returned the content URI to the client:
+</p>
+<pre>
+    ...
+    /*
+     * Get the file's content URI from the incoming Intent, then
+     * get the file's MIME type
+     */
+    Uri returnUri = returnIntent.getData();
+    String mimeType = getContentResolver().getType(returnUri);
+    ...
+</pre>
+<h2 id="RetrieveFileInfo">Retrieve a File's Name and Size</h2>
+<p>
+    The {@link android.support.v4.content.FileProvider} class has a default implementation of the
+    {@link android.support.v4.content.FileProvider#query query()} method that returns the
+    name and size of the file associated with a content URI in a
+    {@link android.database.Cursor}. The default implementation returns two columns:
+</p>
+<dl>
+    <dt>{@link android.provider.OpenableColumns#DISPLAY_NAME DISPLAY_NAME}</dt>
+    <dd>
+        The file's name, as a {@link java.lang.String}. This value is the same as the value returned
+        by {@link java.io.File#getName File.getName()}.
+    </dd>
+    <dt>{@link android.provider.OpenableColumns#SIZE SIZE}</dt>
+    <dd>
+        The size of the file in bytes, as a {@code long} This value is the same as the value
+        returned by {@link java.io.File#length File.length()}
+    </dd>
+</dl>
+<p>
+    The client app can get both the {@link android.provider.OpenableColumns#DISPLAY_NAME
+    DISPLAY_NAME} and {@link android.provider.OpenableColumns#SIZE SIZE} for a file by setting all
+    of the arguments of {@link android.support.v4.content.FileProvider#query query()} to
+    {@code null} except for the content URI. For example, this code snippet retrieves a file's
+    {@link android.provider.OpenableColumns#DISPLAY_NAME DISPLAY_NAME} and
+    {@link android.provider.OpenableColumns#SIZE SIZE} and displays each one in separate
+    {@link android.widget.TextView}:
+</p>
+<pre>
+    ...
+    /*
+     * Get the file's content URI from the incoming Intent,
+     * then query the server app to get the file's display name
+     * and size.
+     */
+    Uri returnUri = returnIntent.getData();
+    Cursor returnCursor =
+            getContentResolver().query(returnUri, null, null, null, null);
+    /*
+     * Get the column indexes of the data in the Cursor,
+     * move to the first row in the Cursor, get the data,
+     * and display it.
+     */
+    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
+    returnCursor.moveToFirst();
+    TextView nameView = (TextView) findViewById(R.id.filename_text);
+    TextView sizeView = (TextView) findViewById(R.id.filesize_text);
+    nameView.setText(returnCursor.getString(nameIndex));
+    sizeView.setText(Long.toString(returnCursor.getLong(sizeIndex)));
+    ...
+</pre>
diff --git a/docs/html/training/secure-file-sharing/setup-sharing.jd b/docs/html/training/secure-file-sharing/setup-sharing.jd
new file mode 100644
index 0000000..8c8fa0f
--- /dev/null
+++ b/docs/html/training/secure-file-sharing/setup-sharing.jd
@@ -0,0 +1,144 @@
+page.title=Setting Up File Sharing
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#DefineProvider">Specify the FileProvider</a></li>
+  <li><a href="#DefineMetaData">Specify Sharable Directories</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}guide/topics/data/data-storage.html">Storage Options</a></li>
+  <li><a href="{@docRoot}training/basics/data-storage/files.html">Saving Files</a>
+</ul>
+
+</div>
+</div>
+
+<p>
+    To securely offer a file from your app to another app, you need to configure your app to offer
+    a secure handle to the file, in the form of a content URI. The Android
+    {@link android.support.v4.content.FileProvider} component generates content URIs for
+    files, based on specifications you provide in XML. This lesson shows you how to add the default
+    implementation of {@link android.support.v4.content.FileProvider} to your app, and how to
+    specify the files you want to offer to other apps.
+</p>
+
+<p class="note">
+  <strong>Note:</strong> The {@link android.support.v4.content.FileProvider} class is part of the
+  <a href="{@docRoot}tools/support-library/features.html#v4">v4 Support Library</a>. For information
+  about including this library in your application, see
+  <a href="{@docRoot}tools/support-library/setup.html">Support Library Setup</a>.
+</p>
+
+<h2 id="DefineProvider">Specify the FileProvider</h2>
+<p>
+    Defining a {@link android.support.v4.content.FileProvider} for your app requires an entry in
+    your manifest. This entry specifies the authority to use in generating content URIs, as well as
+    the name of an XML file that specifies the directories your app can share.
+</p>
+<p>
+    The following snippet shows you how to add to your manifest the
+    <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"
+    >&lt;provider&gt;</a></code> element that specifies the
+    {@link android.support.v4.content.FileProvider} class, the authority, and the
+    XML file name:
+</p>
+<pre>
+&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.myapp"&gt;
+    &lt;application
+        ...&gt;
+        &lt;provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.example.myapp.fileprovider"
+            android:grantUriPermissions="true"
+            android:exported="false"&gt;
+            &lt;meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="&#64;xml/filepaths" /&gt;
+        &lt;/provider&gt;
+        ...
+    &lt;/application&gt;
+&lt;/manifest&gt;</pre>
+<p>
+    In this example, the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#auth"
+    >android:authorities</a></code> attribute specifies the URI authority
+    that you want to use for content URIs generated by the
+    {@link android.support.v4.content.FileProvider}.
+    In the example, the authority is <code>com.example.myapp.fileprovider</code>. For your own
+    app, specify an authority consisting of the app's
+    <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html#package"
+    >android:package</a></code> value with the string "fileprovider" appended to it. To learn more
+    about the authority value, see the topic
+    <a href="{@docRoot}guide/topics/providers/content-provider-basics.html#ContentURIs"
+    >Content URIs</a> and the documentation for the
+    <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#auth"
+    >android:authorities</a></code> attribute.
+</p>
+<p>
+    The <code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"
+    >&lt;meta-data&gt;</a></code> child element of the
+    <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"
+    >&lt;provider&gt;</a></code> points to an XML file that specifies the directories you want to
+    share. The <code>android:resource</code> attribute is the path and name of the file, without
+    the <code>.xml</code> extension.The contents of this file are described in the next section.
+</p>
+<h2 id="DefineMetaData">Specify Sharable Directories</h2>
+<p>
+    Once you have added the {@link android.support.v4.content.FileProvider} to your app manifest,
+    you need to specify the directories that contain the files you want to share. To specify the
+    directories, start by creating the file <code>filepaths.xml</code> in the <code>res/xml/</code>
+    subdirectory of your project. In this file, specify the directories by adding an XML element for
+    each directory. The following snippet shows you an example of the contents of
+    <code>res/xml/filepaths.xml</code>. The snippet also demonstrates how to share a subdirectory
+    of the <code>files/</code> directory in your internal storage area:
+</p>
+<pre>
+&lt;paths&gt;
+    &lt;files-path path="images/" name="myimages" /&gt;
+&lt;/paths&gt;</pre>
+<p>
+    In this example, the <code>&lt;files-path&gt;</code> tag shares directories within the
+    <code>files/</code> directory of your app's internal storage. The <code>path</code> attribute
+    shares the <code>images/</code> subdirectory of <code>files/</code>.  The <code>name</code>
+    attribute tells the {@link android.support.v4.content.FileProvider} to add the path segment
+    <code>myimages</code> to content URIs for files in the <code>files/images/</code> subdirectory.
+</p>
+<p>
+    The <code>&lt;paths&gt;</code> element can have multiple children, each specifying a different
+    directory to share. In addition to the <code>&lt;files-path&gt;</code> element, you can
+    use the <code>&lt;external-path&gt;</code> element to share directories in external storage, and
+    the <code>&lt;cache-path&gt;</code> element to share directories in your internal cache
+    directory. To learn more about the child elements that specify shared directories, see the
+    {@link android.support.v4.content.FileProvider} reference documentation.
+</p>
+<p class="note">
+    <strong>Note:</strong> The XML file is the only way you can specify the directories you want to
+    share; you can't programmatically add a directory.
+</p>
+<p>
+    You now have a complete specification of a {@link android.support.v4.content.FileProvider}
+    that generates content URIs for files in the <code>files/</code> directory of your app's
+    internal storage or for files in subdirectories of <code>files/</code>. When your app generates
+    a content URI for a file, it contains the authority specified in the
+    <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"
+    >&lt;provider&gt;</a></code> element (<code>com.example.myapp.fileprovider</code>),
+    the path <code>myimages/</code>, and the name of the file.
+</p>
+<p>
+    For example, if you define a {@link android.support.v4.content.FileProvider} according to the
+    snippets in this lesson, and you request a content URI for the file
+    <code>default_image.jpg</code>, {@link android.support.v4.content.FileProvider} returns the
+    following URI:
+</p>
+<pre>
+content://com.example.myapp.fileprovider/myimages/default_image.jpg</pre>
+
diff --git a/docs/html/training/secure-file-sharing/share-file.jd b/docs/html/training/secure-file-sharing/share-file.jd
new file mode 100644
index 0000000..6c52770
--- /dev/null
+++ b/docs/html/training/secure-file-sharing/share-file.jd
@@ -0,0 +1,298 @@
+page.title=Sharing a File
+
+trainingnavtop=true
+@jd:body
+
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#ReceiveRequests">Receive File Requests</a></li>
+  <li><a href="#CreateFileSelection">Create a File Selection Activity</a></li>
+  <li><a href="#RespondToRequest">Respond to a File Selection</a></li>
+  <li><a href="#GrantPermissions">Grant Permissions for the File</a></li>
+  <li><a href="#ShareFile">Share the File with the Requesting App</a>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+    <li>
+        <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContentURI"
+        >Designing Content URIs</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#Permissions"
+        >Implementing Content Provider Permissions</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a>
+    </li>
+    <li>
+        <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
+    </li>
+</ul>
+
+</div>
+</div>
+<p>
+    Once you have set up your app to share files using content URIs, you can respond to other apps'
+    requests for those files. One way to respond to these requests is to provide a file selection
+    interface from the server app that other applications can invoke. This approach allows a client
+    application to let users select a file from the server app and then receive the selected file's
+    content URI.
+</p>
+<p>
+    This lesson shows you how to create a file selection {@link android.app.Activity} in your app
+    that responds to requests for files.
+</p>
+<h2 id="ReceiveRequests">Receive File Requests</h2>
+<p>
+    To receive requests for files from client apps and respond with a content URI, your app should
+    provide a file selection {@link android.app.Activity}. Client apps start this
+    {@link android.app.Activity} by calling {@link android.app.Activity#startActivityForResult
+    startActivityForResult()} with an {@link android.content.Intent} containing the action
+    {@link android.content.Intent#ACTION_PICK ACTION_PICK}. When the client app calls
+    {@link android.app.Activity#startActivityForResult startActivityForResult()}, your app can
+    return a result to the client app, in the form of a content URI for the file the user selected.
+</p>
+<p>
+    To learn how to implement a request for a file in a client app, see the lesson
+    <a href="request-file.html">Requesting a Shared File</a>.
+</p>
+<h2 id="CreateFileSelection">Create a File Selection Activity</h2>
+<p>
+    To set up the file selection {@link android.app.Activity}, start by specifying the
+    {@link android.app.Activity} in your manifest, along with an intent filter
+    that matches the action {@link android.content.Intent#ACTION_PICK ACTION_PICK} and the
+    categories {@link android.content.Intent#CATEGORY_DEFAULT CATEGORY_DEFAULT} and
+    {@link android.content.Intent#CATEGORY_OPENABLE CATEGORY_OPENABLE}.  Also add MIME type filters
+    for the files your app serves to other apps. The following snippet shows you how to specify the
+    new {@link android.app.Activity} and intent filter:
+</p>
+<pre>
+&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+    ...
+        &lt;application&gt;
+        ...
+            &lt;activity
+                android:name=".FileSelectActivity"
+                android:label="&#64;"File Selector" &gt;
+                &lt;intent-filter&gt;
+                    &lt;action
+                        android:name="android.intent.action.PICK"/&gt;
+                    &lt;category
+                        android:name="android.intent.category.DEFAULT"/&gt;
+                    &lt;category
+                        android:name="android.intent.category.OPENABLE"/&gt;
+                    &lt;data android:mimeType="text/plain"/&gt;
+                    &lt;data android:mimeType="image/*"/&gt;
+                &lt;/intent-filter&gt;
+            &lt;/activity&gt;</pre>
+<h3>Define the file selection Activity in code</h3>
+<p>
+    Next, define an {@link android.app.Activity} subclass that displays the files available from
+    your app's <code>files/images/</code> directory in internal storage and allows the user to pick
+    the desired file. The following snippet demonstrates how to define this
+    {@link android.app.Activity} and respond to the user's selection:
+</p>
+<pre>
+public class MainActivity extends Activity {
+    // The path to the root of this app's internal storage
+    private File mPrivateRootDir;
+    // The path to the "images" subdirectory
+    private File mImagesDir;
+    // Array of files in the images subdirectory
+    File[] mImageFiles;
+    // Array of filenames corresponding to mImageFiles
+    String[] mImageFilenames;
+    // Initialize the Activity
+    &#64;Override
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // Set up an Intent to send back to apps that request a file
+        mResultIntent =
+                new Intent("com.example.myapp.ACTION_RETURN_FILE");
+        // Get the files/ subdirectory of internal storage
+        mPrivateRootDir = getFilesDir();
+        // Get the files/images subdirectory;
+        mImagesDir = new File(mPrivateRootDir, "images");
+        // Get the files in the images subdirectory
+        mImageFiles = mImagesDir.listFiles();
+        // Set the Activity's result to null to begin with
+        setResult(Activity.RESULT_CANCELED, null);
+        /*
+         * Display the file names in the ListView mFileListView.
+         * Back the ListView with the array mImageFilenames, which
+         * you can create by iterating through mImageFiles and
+         * calling File.getAbsolutePath() for each File
+         */
+         ...
+    }
+    ...
+}</pre>
+<h2 id="RespondToRequest">Respond to a File Selection</h2>
+<p>
+    Once a user selects a shared file, your application must determine what file was selected and
+    then generate a content URI for the file. Since the {@link android.app.Activity} displays the
+    list of available files in a {@link android.widget.ListView}, when the user clicks a file name
+    the system calls the method {@link android.widget.AdapterView.OnItemClickListener#onItemClick
+    onItemClick()}, in which you can get the selected file.
+</p>
+<p>
+    In {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, get a
+    {@link java.io.File} object for the file name of the selected file and pass it as an argument to
+    {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()}, along with the
+    authority that you specified in the
+    <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"
+    >&lt;provider&gt;</a></code> element for the {@link android.support.v4.content.FileProvider}.
+    The resulting content URI contains the authority, a path segment corresponding to the file's
+    directory (as specified in the XML meta-data), and the name of the file including its
+    extension. How {@link android.support.v4.content.FileProvider} maps directories to path
+    segments based on XML meta-data is described in the section
+    <a href="setup-sharing.html#DefineMetaData">Specify Sharable Directories</a>.
+</p>
+<p>
+    The following snippet shows you how to detect the selected file and get a content URI for it:
+</p>
+<pre>
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // Define a listener that responds to clicks on a file in the ListView
+        mFileListView.setOnItemClickListener(
+                new AdapterView.OnItemClickListener() {
+            &#64;Override
+            /*
+             * When a filename in the ListView is clicked, get its
+             * content URI and send it to the requesting app
+             */
+            public void onItemClick(AdapterView&lt;?&gt; adapterView,
+                    View view,
+                    int position,
+                    long rowId) {
+                /*
+                 * Get a File for the selected file name.
+                 * Assume that the file names are in the
+                 * mImageFilename array.
+                 */
+                File requestFile = new File(mImageFilename[position]);
+                /*
+                 * Most file-related method calls need to be in
+                 * try-catch blocks.
+                 */
+                // Use the FileProvider to get a content URI
+                try {
+                    fileUri = FileProvider.getUriForFile(
+                            MainActivity.this,
+                            "com.example.myapp.fileprovider",
+                            requestFile);
+                } catch (IllegalArgumentException e) {
+                    Log.e("File Selector",
+                          "The selected file can't be shared: " +
+                          clickedFilename);
+                }
+                ...
+            }
+        });
+        ...
+    }</pre>
+<p>
+    Remember that you can only generate content URIs for files that reside in a directory
+    you've specified in the meta-data file that contains the <code>&lt;paths&gt;</code> element, as
+    described in the section <a href="setup-sharing.html#DefineMetaData"
+    >Specify Sharable Directories</a>. If you call
+    {@link android.support.v4.content.FileProvider#getUriForFile getUriForFile()} for a
+    {@link java.io.File} in a path that you haven't specified, you receive an
+    {@link java.lang.IllegalArgumentException}.
+</p>
+<h2 id="GrantPermissions">Grant Permissions for the File</h2>
+<p>
+    Now that you have a content URI for the file you want to share with another app, you need to
+    allow the client app to access the file. To allow access, grant permissions to the client app by
+    adding the content URI to an {@link android.content.Intent} and then setting permission flags on
+    the {@link android.content.Intent}. The permissions you grant are temporary and expire
+    automatically when the receiving app's task stack is finished.
+</p>
+<p>
+    The following code snippet shows you how to set read permission for the file:
+</p>
+<pre>
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // Define a listener that responds to clicks in the ListView
+        mFileListView.setOnItemClickListener(
+                new AdapterView.OnItemClickListener() {
+            &#64;Override
+            public void onItemClick(AdapterView&lt;?&gt; adapterView,
+                    View view,
+                    int position,
+                    long rowId) {
+                ...
+                if (fileUri != null) {
+                    // Grant temporary read permission to the content URI
+                    mResultIntent.addFlags(
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                }
+                ...
+             }
+             ...
+        });
+    ...
+    }</pre>
+<p class="caution">
+    <strong>Caution:</strong> Calling {@link android.content.Intent#setFlags setFlags()} is the only
+    way to securely grant access to your files using temporary access permissions. Avoid calling
+    {@link android.content.Context#grantUriPermission Context.grantUriPermission()} method for a
+    file's content URI, since this method grants access that you can only revoke by
+    calling {@link android.content.Context#revokeUriPermission Context.revokeUriPermission()}.
+</p>
+<h2 id="ShareFile">Share the File with the Requesting App</h2>
+<p>
+    To share the file with the app that requested it, pass the {@link android.content.Intent}
+    containing the content URI and permissions to {@link android.app.Activity#setResult
+    setResult()}. When the {@link android.app.Activity} you have just defined is finished, the
+    system sends the {@link android.content.Intent} containing the content URI to the client app.
+    The following code snippet shows you how to do this:
+</p>
+<pre>
+    protected void onCreate(Bundle savedInstanceState) {
+        ...
+        // Define a listener that responds to clicks on a file in the ListView
+        mFileListView.setOnItemClickListener(
+                new AdapterView.OnItemClickListener() {
+            &#64;Override
+            public void onItemClick(AdapterView&lt;?&gt; adapterView,
+                    View view,
+                    int position,
+                    long rowId) {
+                ...
+                if (fileUri != null) {
+                    ...
+                    // Put the Uri and MIME type in the result Intent
+                    mResultIntent.setDataAndType(
+                            fileUri,
+                            getContentResolver().getType(fileUri));
+                    // Set the result
+                    MainActivity.this.setResult(Activity.RESULT_OK,
+                            mResultIntent);
+                    } else {
+                        mResultIntent.setDataAndType(null, "");
+                        MainActivity.this.setResult(RESULT_CANCELED,
+                                mResultIntent);
+                    }
+                }
+        });</pre>
+<p>
+    Provide users with an way to return immediately to the client app once they have chosen a file.
+    One way to do this is to provide a checkmark or <b>Done</b> button. Associate a method with
+    the button using the button's
+    <code><a href="{@docRoot}reference/android/view/View.html#attr_android:onClick"
+    >android:onClick</a></code> attribute. In the method, call
+    {@link android.app.Activity#finish finish()}. For example:
+</p>
+<pre>
+    public void onDoneClick(View v) {
+        // Associate a method with the Done button
+        finish();
+    }</pre>
diff --git a/docs/html/training/sharing/index.jd b/docs/html/training/sharing/index.jd
index 2aa22b6..06d42fc 100644
--- a/docs/html/training/sharing/index.jd
+++ b/docs/html/training/sharing/index.jd
@@ -1,4 +1,4 @@
-page.title=Sharing Content
+page.title=Sharing Simple Data
 page.tags="intents","share"
 
 trainingnavtop=true
@@ -20,26 +20,26 @@
 </div>
 </div>
 
- 
+
 <p>One of the great things about Android applications is their ability to communicate and
 integrate with each other. Why reinvent functionality that isn't core to your application when it
-already exists in another application?</p> 
+already exists in another application?</p>
 
-<p>This class covers some common ways you can send and receive content between
+<p>This class covers some common ways you can send and receive simple data between
 applications using {@link android.content.Intent} APIs and the {@link
 android.view.ActionProvider} object.</p>
 
 
 <h2>Lessons</h2>
- 
-<dl> 
-  <dt><b><a href="send.html">Sending Content to Other Apps</a></b></dt> 
-    <dd>Learn how to set up your application to be able to send text and binary data to other
-applications with intents.</dd> 
- 
-  <dt><b><a href="receive.html">Receiving Content from Other Apps</a></b></dt> 
-    <dd>Learn how to set up your application to receive text and binary data from intents.</dd> 
 
-  <dt><b><a href="shareaction.html">Adding an Easy Share Action</a></b></dt> 
-    <dd>Learn how to add a "share" action item to your action bar.</dd> 
-</dl> 
+<dl>
+  <dt><b><a href="send.html">Sending Simple Data to Other Apps</a></b></dt>
+    <dd>Learn how to set up your application to be able to send text and binary data to other
+applications with intents.</dd>
+
+  <dt><b><a href="receive.html">Receiving Simple Data from Other Apps</a></b></dt>
+    <dd>Learn how to set up your application to receive text and binary data from intents.</dd>
+
+  <dt><b><a href="shareaction.html">Adding an Easy Share Action</a></b></dt>
+    <dd>Learn how to add a "share" action item to your action bar.</dd>
+</dl>
diff --git a/docs/html/training/sharing/receive.jd b/docs/html/training/sharing/receive.jd
index 7ec3def..8c5f862 100644
--- a/docs/html/training/sharing/receive.jd
+++ b/docs/html/training/sharing/receive.jd
@@ -1,5 +1,5 @@
-page.title=Receiving Content from Other Apps
-parent.title=Sharing Content
+page.title=Receiving Simple Data from Other Apps
+parent.title=Sharing Simple Data
 parent.link=index.html
 
 trainingnavtop=true
@@ -30,26 +30,26 @@
 </div>
 </div>
 
-<p>Just as your application can send data to other applications, so too can it easily receive data 
-from applications. Think about how users interact with your application, and what data types you 
-want to receive from other applications. For example, a social networking application would likely 
-be interested in receiving text content, like an interesting web URL, from another app. The 
+<p>Just as your application can send data to other applications, so too can it easily receive data
+from applications. Think about how users interact with your application, and what data types you
+want to receive from other applications. For example, a social networking application would likely
+be interested in receiving text content, like an interesting web URL, from another app. The
 <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.plus">Google+ Android
-application</a> 
-accepts both text <em>and</em> single or multiple images. With this app, a user can easily start a 
+application</a>
+accepts both text <em>and</em> single or multiple images. With this app, a user can easily start a
 new Google+ post with photos from the Android Gallery app.</p>
 
 
 <h2 id="update-manifest">Update Your Manifest</h2>
 
-<p>Intent filters inform the system what intents an application component is willing to accept. 
-Similar to how you constructed an intent with action {@link android.content.Intent#ACTION_SEND} in 
-the <a href="{@docRoot}training/sharing/send.html">Send Content to Other Apps Using Intents</a> 
-lesson, you create intent filters in order to be able to receive intents with this action. You 
-define an intent filter in your manifest, using the 
+<p>Intent filters inform the system what intents an application component is willing to accept.
+Similar to how you constructed an intent with action {@link android.content.Intent#ACTION_SEND} in
+the <a href="{@docRoot}training/sharing/send.html">Sending Simple Data to Other Apps</a>
+lesson, you create intent filters in order to be able to receive intents with this action. You
+define an intent filter in your manifest, using the
 <code><a
-href="{@docRoot}guide/components/intents-filters.html#ifs">&lt;intent-filter&gt;</a></code> 
-element. For example, if your application handles receiving text content, a single image of any 
+href="{@docRoot}guide/components/intents-filters.html#ifs">&lt;intent-filter&gt;</a></code>
+element. For example, if your application handles receiving text content, a single image of any
 type, or multiple images of any type, your manifest would look like:</p>
 
 <pre>
@@ -72,24 +72,24 @@
 &lt;/activity&gt;
 </pre>
 
-<p class="note"><strong>Note:</strong> For more information on intent filters and intent resolution 
+<p class="note"><strong>Note:</strong> For more information on intent filters and intent resolution
 please read <a href="{@docRoot}guide/components/intents-filters.html#ifs">Intents and Intent
 Filters</a></p>
 
 <p>When another application tries to share any of these things by constructing an intent and passing
 it to {@link android.content.Context#startActivity(android.content.Intent) startActivity()}, your
-application will be listed as an option in the intent chooser. If the user selects your application, 
-the corresponding activity (<code>.ui.MyActivity</code> in the example above) will be started. It 
+application will be listed as an option in the intent chooser. If the user selects your application,
+the corresponding activity (<code>.ui.MyActivity</code> in the example above) will be started. It
 is then up to you to handle the content appropriately within your code and UI.</p>
 
 
 <h2 id="handling-content">Handle the Incoming Content</h2>
 
 <p>To handle the content delivered by an {@link android.content.Intent}, start by calling {@link
-android.content.Intent#getIntent(String) getIntent()} 
-to get {@link android.content.Intent} object. Once you have the object, you can examine its 
-contents to determine what to do next. Keep in mind that if this activity can be started from other 
-parts of the system, such as the launcher, then you will need to take this into consideration when 
+android.content.Intent#getIntent(String) getIntent()}
+to get {@link android.content.Intent} object. Once you have the object, you can examine its
+contents to determine what to do next. Keep in mind that if this activity can be started from other
+parts of the system, such as the launcher, then you will need to take this into consideration when
 examining the intent.</p>
 
 <pre>
@@ -143,7 +143,7 @@
 image being sent might be extremely large. Also, remember to process binary data in a separate
 thread rather than the main ("UI") thread.</p>
 
-<p>Updating the UI can be as simple as populating an {@link android.widget.EditText}, or it can 
-be more complicated like applying an interesting photo filter to an image. It's really specific 
+<p>Updating the UI can be as simple as populating an {@link android.widget.EditText}, or it can
+be more complicated like applying an interesting photo filter to an image. It's really specific
 to your application what happens next.</p>
 
diff --git a/docs/html/training/sharing/send.jd b/docs/html/training/sharing/send.jd
index ed9e12e..f5da68f 100644
--- a/docs/html/training/sharing/send.jd
+++ b/docs/html/training/sharing/send.jd
@@ -1,9 +1,9 @@
-page.title=Sending Content to Other Apps
-parent.title=Sharing Content
+page.title=Sending Simple Data to Other Apps
+parent.title=Sharing Simple Data
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Receiving Content from Other Apps
+next.title=Receiving Simple Data from Other Apps
 next.link=receive.html
 
 @jd:body
@@ -29,22 +29,22 @@
 </div>
 </div>
 
-<p>When you construct an intent, you must specify the action you want the intent to "trigger." 
-Android defines several actions, including {@link android.content.Intent#ACTION_SEND} which, as 
-you can probably guess, indicates that the intent is sending data from one activity to another, 
-even across process boundaries. To send data to another activity, all you need to do is specify 
-the data and its type, the system will identify compatible receiving activities and display them 
-to the user (if there are multiple options) or immediately start the activity (if there is only 
-one option). Similarly, you can advertise the data types that your activities support receiving 
+<p>When you construct an intent, you must specify the action you want the intent to "trigger."
+Android defines several actions, including {@link android.content.Intent#ACTION_SEND} which, as
+you can probably guess, indicates that the intent is sending data from one activity to another,
+even across process boundaries. To send data to another activity, all you need to do is specify
+the data and its type, the system will identify compatible receiving activities and display them
+to the user (if there are multiple options) or immediately start the activity (if there is only
+one option). Similarly, you can advertise the data types that your activities support receiving
 from other applications by specifying them in your manifest.</p>
 
-<p>Sending and receiving data between applications with intents is most commonly used for social 
-sharing of content. Intents allow users to share information quickly and easily, using their 
+<p>Sending and receiving data between applications with intents is most commonly used for social
+sharing of content. Intents allow users to share information quickly and easily, using their
 favorite applications.</p>
 
-<p><strong>Note:</strong> The best way to add a share action item to an 
-{@link android.app.ActionBar} is to use {@link android.widget.ShareActionProvider}, which became 
-available in API level 14. {@link android.widget.ShareActionProvider} is discussed in the lesson 
+<p><strong>Note:</strong> The best way to add a share action item to an
+{@link android.app.ActionBar} is to use {@link android.widget.ShareActionProvider}, which became
+available in API level 14. {@link android.widget.ShareActionProvider} is discussed in the lesson
 about <a href="shareaction.html">Adding an Easy Share Action</a>.</p>
 
 
@@ -58,10 +58,10 @@
 </p>
 </div>
 
-<p>The most straightforward and common use of the {@link android.content.Intent#ACTION_SEND} 
-action is sending text content from one activity to another. For example, the built-in Browser 
-app can share the URL of the currently-displayed page as text with any application. This is useful 
-for sharing an article or website with friends via email or social networking. Here is the code to 
+<p>The most straightforward and common use of the {@link android.content.Intent#ACTION_SEND}
+action is sending text content from one activity to another. For example, the built-in Browser
+app can share the URL of the currently-displayed page as text with any application. This is useful
+for sharing an article or website with friends via email or social networking. Here is the code to
 implement this type of sharing:</p>
 
 <pre>
@@ -72,12 +72,12 @@
 startActivity(sendIntent);
 </pre>
 
-<p>If there's an installed application with a filter that matches 
-{@link android.content.Intent#ACTION_SEND} and MIME type text/plain, the Android system will run 
-it; if more than one application matches, the system displays a disambiguation dialog (a "chooser") 
-that allows the user to choose an app. If you call 
+<p>If there's an installed application with a filter that matches
+{@link android.content.Intent#ACTION_SEND} and MIME type text/plain, the Android system will run
+it; if more than one application matches, the system displays a disambiguation dialog (a "chooser")
+that allows the user to choose an app. If you call
 {@link android.content.Intent#createChooser(android.content.Intent, CharSequence)
-Intent.createChooser()} 
+Intent.createChooser()}
 for the intent, Android will <strong>always</strong> display the chooser. This has some
 advantages:</p>
 
@@ -100,17 +100,17 @@
 
 <p>The resulting dialog is shown in figure 1.</p>
 
-<p>Optionally, you can set some standard extras for the intent: 
-{@link android.content.Intent#EXTRA_EMAIL}, {@link android.content.Intent#EXTRA_CC}, 
-{@link android.content.Intent#EXTRA_BCC}, {@link android.content.Intent#EXTRA_SUBJECT}. However, 
-if the receiving application is not designed to use them, nothing will happen. You can use 
-custom extras as well, but there's no effect unless the receiving application understands them. 
+<p>Optionally, you can set some standard extras for the intent:
+{@link android.content.Intent#EXTRA_EMAIL}, {@link android.content.Intent#EXTRA_CC},
+{@link android.content.Intent#EXTRA_BCC}, {@link android.content.Intent#EXTRA_SUBJECT}. However,
+if the receiving application is not designed to use them, nothing will happen. You can use
+custom extras as well, but there's no effect unless the receiving application understands them.
 Typically, you'd use custom extras defined by the receiving application itself.</p>
 
-<p class="note"><strong>Note:</strong> Some e-mail applications, such as Gmail, expect a 
-{@link java.lang.String String[]} for extras like {@link android.content.Intent#EXTRA_EMAIL} and 
-{@link android.content.Intent#EXTRA_CC}, use 
-{@link android.content.Intent#putExtra(String,String[]) putExtra(String, String[])} to add these 
+<p class="note"><strong>Note:</strong> Some e-mail applications, such as Gmail, expect a
+{@link java.lang.String String[]} for extras like {@link android.content.Intent#EXTRA_EMAIL} and
+{@link android.content.Intent#EXTRA_CC}, use
+{@link android.content.Intent#putExtra(String,String[]) putExtra(String, String[])} to add these
 to your intent.</p>
 
 
diff --git a/docs/html/training/sharing/shareaction.jd b/docs/html/training/sharing/shareaction.jd
index 873f6145..ee811da 100644
--- a/docs/html/training/sharing/shareaction.jd
+++ b/docs/html/training/sharing/shareaction.jd
@@ -3,7 +3,7 @@
 parent.link=index.html
 
 trainingnavtop=true
-previous.title=Receiving Content from Other Apps
+previous.title=Receiving Simple Data from Other Apps
 previous.link=receive.html
 
 @jd:body
@@ -28,7 +28,7 @@
 </div>
 
 
-<p>Implementing an effective and user friendly share action in your {@link android.app.ActionBar} 
+<p>Implementing an effective and user friendly share action in your {@link android.app.ActionBar}
 is made even easier with the introduction of {@link  android.view.ActionProvider} in Android 4.0
 (API Level 14). An {@link android.view.ActionProvider}, once attached to a menu item in the action
 bar, handles both the appearance and behavior of that item. In the case of {@link
@@ -47,36 +47,48 @@
 
 <h2 id="update-menus">Update Menu Declarations</h2>
 
-<p>To get started with {@link android.widget.ShareActionProvider ShareActionProviders}, define the <code>android:actionProviderClass</code> attribute for the corresponding <code>&lt;item&gt;</code> in your <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a> file:</p>
+<p>
+    To get started with {@link android.widget.ShareActionProvider ShareActionProviders},
+    define the <code>android:actionProviderClass</code> attribute for the corresponding
+    <code>&lt;item&gt;</code> in your <a href="{@docRoot}guide/topics/resources/menu-resource.html"
+    >menu resource</a> file:</p>
 
 <pre>
 &lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
-    &lt;item android:id=&quot;@+id/menu_item_share&quot;
-        android:showAsAction=&quot;ifRoom&quot;
-        android:title=&quot;Share&quot;
-        <strong>android:actionProviderClass=&quot;android.widget.ShareActionProvider&quot;</strong> /&gt;
+    &lt;item
+            android:id=&quot;@+id/menu_item_share&quot;
+            android:showAsAction=&quot;ifRoom&quot;
+            android:title=&quot;Share&quot;
+            <b>android:actionProviderClass=
+                "android.widget.ShareActionProvider"</b> /&gt;
     ...
 &lt;/menu&gt;
 </pre>
 
-<p>This delegates responsibility for the item's appearance and function to 
-{@link android.widget.ShareActionProvider}. However, you will need to tell the provider what you 
-would like to share.</p>
+<p>
+    This delegates responsibility for the item's appearance and function to
+    {@link android.widget.ShareActionProvider}. However, you will need to tell the provider what you
+    would like to share.
+</p>
 
 
 <h2 id="set-share-intent">Set the Share Intent</h2>
 
-<p>In order for {@link android.widget.ShareActionProvider} to function, you must provide it a share
-intent. This share intent should be the same as described in the <a
-href="{@docRoot}training/sharing/send.html">Sending Content to Other Apps</a>
-lesson, with action {@link android.content.Intent#ACTION_SEND} and additional data set via extras
-like {@link android.content.Intent#EXTRA_TEXT} and {@link android.content.Intent#EXTRA_STREAM}. To
-assign a share intent, first find the corresponding {@link android.view.MenuItem} while inflating
-your menu resource in your {@link android.app.Activity} or {@link android.app.Fragment}. Next, call
-{@link android.view.MenuItem#getActionProvider() MenuItem.getActionProvider()} to retreive an
-instance of {@link android.widget.ShareActionProvider}. Use {@link
-android.widget.ShareActionProvider#setShareIntent(android.content.Intent) setShareIntent()} to
-update the share intent associated with that action item. Here's an example:</p>
+<p>
+    In order for {@link android.widget.ShareActionProvider} to function, you must provide it a share
+    intent. This share intent should be the same as described in the
+    <a href="{@docRoot}training/sharing/send.html">Sending Simple Data to Other Apps</a> lesson,
+    with action {@link android.content.Intent#ACTION_SEND} and additional data set via extras
+    like {@link android.content.Intent#EXTRA_TEXT} and {@link android.content.Intent#EXTRA_STREAM}.
+    To assign a share intent, first find the corresponding {@link android.view.MenuItem} while
+    inflating your menu resource in your {@link android.app.Activity} or
+    {@link android.app.Fragment}. Next, call {@link android.view.MenuItem#getActionProvider
+    MenuItem.getActionProvider()} to retrieve an instance of
+    {@link android.widget.ShareActionProvider}. Use
+    {@link android.widget.ShareActionProvider#setShareIntent(android.content.Intent)
+    setShareIntent()} to update the share intent associated with that action item. Here's an
+    example:
+</p>
 
 <pre>
 private ShareActionProvider mShareActionProvider;
@@ -105,8 +117,8 @@
 }
 </pre>
 
-<p>You may only need to set the share intent once during the creation of your menus, or you may 
-want to set it and then update it as the UI changes. For example, when you view photos full screen 
+<p>You may only need to set the share intent once during the creation of your menus, or you may
+want to set it and then update it as the UI changes. For example, when you view photos full screen
 in the Gallery app, the sharing intent changes as you flip between photos.</p>
 
 <p>For further discussion about the {@link android.widget.ShareActionProvider} object, see the <a
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 77ac235..39386bc 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -192,33 +192,91 @@
         </ul>
       </li>
 
-      <li class="nav-section">
-        <div class="nav-section-header">
-          <a href="<?cs var:toroot ?>training/sharing/index.html"
-             description=
-             "How to take your app interaction to the next level by sharing
-             information with other apps, receive information back, and provide a simple and
-             scalable way to perform Share actions with user content."
-            >Sharing Content</a>
-        </div>
-        <ul>
-          <li><a href="<?cs var:toroot ?>training/sharing/send.html">
-            Sending Content to Other Apps
-          </a>
-          </li>
-          <li><a href="<?cs var:toroot ?>training/sharing/receive.html">
-            Receiving Content from Other Apps
-          </a>
-          </li>
-          <li><a href="<?cs var:toroot ?>training/sharing/shareaction.html">
-            Adding an Easy Share Action
-          </a>
-          </li>
-        </ul>
-      </li>
     </ul>
   </li><!-- end getting started -->
-
+    <li class="nav-section">
+        <div class="nav-section-header">
+            <a href="<?cs var:toroot ?>training/building-content-sharing.html">
+            <span class="small">Building Apps with</span><br/>Content Sharing
+            </a>
+        </div>
+        <ul>
+            <li class="nav-section">
+                <div class="nav-section-header">
+                    <a href="<?cs var:toroot ?>training/sharing/index.html"
+                    description=
+                    "How to take your app interaction to the next level by sharing
+                    information with other apps, receive information back, and provide a simple and
+                    scalable way to perform Share actions with user content."
+                    >Sharing Simple Data</a>
+                </div>
+                <ul>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/sharing/send.html">
+                        Sending Simple Data to Other Apps
+                        </a>
+                    </li>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/sharing/receive.html">
+                        Receiving Simple Data from Other Apps
+                        </a>
+                    </li>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/sharing/shareaction.html">
+                        Adding an Easy Share Action
+                        </a>
+                    </li>
+                </ul>
+            </li>
+            <li class="nav-section">
+                <div class="nav-section-header">
+                    <a href="<?cs var:toroot?>training/secure-file-sharing/index.html"
+                    description=
+                    "How to provide secure access to a file associated with your app using a content
+                    URI and temporary access permissions."
+                    >Sharing Files</a>
+                </div>
+                <ul>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/secure-file-sharing/setup-sharing.html">
+                        Setting Up File Sharing
+                        </a>
+                    </li>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/secure-file-sharing/share-file.html">
+                        Sharing a File
+                        </a>
+                    </li>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/secure-file-sharing/request-file.html">
+                        Requesting a Shared File
+                        </a>
+                    </li>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/secure-file-sharing/retrieve-info.html">
+                        Retrieving File Information
+                        </a>
+                    </li>
+                </ul>
+            </li>
+            <li class="nav-section">
+                <div class="nav-section-header">
+                    <a href="<?cs var:toroot ?>training/beam-files/index.html"
+                    description=
+                    "How to transfer files between devices using the NFC Android Beam feature."
+                    >Sharing Files with NFC</a>
+                </div>
+                <ul>
+                    <li>
+                        <a href="<?cs var:toroot ?>training/beam-files/send-files.html"
+                        >Sending Files to Another Device</a>
+                    </li>
+                    <li><a href="<?cs var:toroot ?>training/beam-files/receive-files.html"
+                    >Receiving Files from Another Device</a></li>
+                </ul>
+            </li>
+        </ul>
+    </li>
 
 
 
@@ -423,7 +481,6 @@
           </li>
         </ul>
       </li>
-
        <li class="nav-section">
         <div class="nav-section-header">
           <a href="<?cs var:toroot ?>training/basics/network-ops/index.html"
@@ -498,7 +555,7 @@
            "How to design a robust conflict resolution strategy for apps that save data to the cloud."
            >Resolving Cloud Save Conflicts
           </a>
-          </li>
+        </li>
       </li>
       <li class="nav-section">
         <div class="nav-section-header">
@@ -1047,6 +1104,14 @@
     <ul>
 
       <li>
+        <a href="<?cs var:toroot ?>training/articles/memory.html"
+           description=
+           "How to keep your app's memory footprint small in order to improve performance
+           on a variety of mobile devices."
+          >Managing Your App's Memory</a>
+      </li>
+
+      <li>
         <a href="<?cs var:toroot ?>training/articles/perf-tips.html"
            description=
            "How to optimize your app's performance in various ways to improve its
@@ -1181,7 +1246,6 @@
       </a>
     </div>
     <ul>
-
       <li>
         <a href="<?cs var:toroot ?>training/articles/security-tips.html"
            description=
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 12b3517..bb1e743 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -411,14 +411,6 @@
     }
 
     /**
-     * Delete once code is updated.
-     * @hide
-     */
-    public void ioSendOutput() {
-        ioSend();
-    }
-
-    /**
      * Receive the latest input into the Allocation. This operation
      * is only valid if {@link #USAGE_IO_INPUT} is set on the Allocation.
      *
diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java
index e023739..db4b577 100644
--- a/graphics/java/android/renderscript/Type.java
+++ b/graphics/java/android/renderscript/Type.java
@@ -216,6 +216,81 @@
     }
 
     /**
+     * Utility function for creating basic 1D types. The type is
+     * created without mipmaps enabled.
+     *
+     * @param rs The RenderScript context
+     * @param e The Element for the Type
+     * @param dimX The X dimension, must be > 0
+     *
+     * @return Type
+     */
+    static public Type createX(RenderScript rs, Element e, int dimX) {
+        if (dimX < 1) {
+            throw new RSInvalidStateException("Dimension must be >= 1.");
+        }
+
+        int id = rs.nTypeCreate(e.getID(rs), dimX, 0, 0, false, false, 0);
+        Type t = new Type(id, rs);
+        t.mElement = e;
+        t.mDimX = dimX;
+        t.calcElementCount();
+        return t;
+    }
+
+    /**
+     * Utility function for creating basic 2D types. The type is
+     * created without mipmaps or cubemaps.
+     *
+     * @param rs The RenderScript context
+     * @param e The Element for the Type
+     * @param dimX The X dimension, must be > 0
+     * @param dimY The Y dimension, must be > 0
+     *
+     * @return Type
+     */
+    static public Type createXY(RenderScript rs, Element e, int dimX, int dimY) {
+        if ((dimX < 1) || (dimY < 1)) {
+            throw new RSInvalidStateException("Dimension must be >= 1.");
+        }
+
+        int id = rs.nTypeCreate(e.getID(rs), dimX, dimY, 0, false, false, 0);
+        Type t = new Type(id, rs);
+        t.mElement = e;
+        t.mDimX = dimX;
+        t.mDimY = dimY;
+        t.calcElementCount();
+        return t;
+    }
+
+    /**
+     * Utility function for creating basic 3D types. The type is
+     * created without mipmaps.
+     *
+     * @param rs The RenderScript context
+     * @param e The Element for the Type
+     * @param dimX The X dimension, must be > 0
+     * @param dimY The Y dimension, must be > 0
+     * @param dimZ The Z dimension, must be > 0
+     *
+     * @return Type
+     */
+    static public Type createXYZ(RenderScript rs, Element e, int dimX, int dimY, int dimZ) {
+        if ((dimX < 1) || (dimY < 1) || (dimZ < 1)) {
+            throw new RSInvalidStateException("Dimension must be >= 1.");
+        }
+
+        int id = rs.nTypeCreate(e.getID(rs), dimX, dimY, dimZ, false, false, 0);
+        Type t = new Type(id, rs);
+        t.mElement = e;
+        t.mDimX = dimX;
+        t.mDimY = dimY;
+        t.mDimZ = dimZ;
+        t.calcElementCount();
+        return t;
+    }
+
+    /**
      * Builder class for Type.
      *
      */
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 411c133..ce8364e 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -58,6 +58,7 @@
 		external/skia/include/utils
 
 	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
+	LOCAL_CFLAGS += -Wno-unused-parameter
 	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
 	LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui
 	LOCAL_MODULE := libhwui
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 0916942..ffd1e8c 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -115,7 +115,7 @@
 // Callbacks
 ///////////////////////////////////////////////////////////////////////////////
 
-void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
+void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) {
     if (texture) {
         const uint32_t size = texture->width * texture->height * bytesPerPixel();
         mSize -= size;
@@ -185,7 +185,7 @@
         mCache.removeOldest();
     }
 
-    generateTexture(colors, positions, count, texture);
+    generateTexture(colors, positions, texture);
 
     mSize += size;
     mCache.put(gradient, texture);
@@ -238,8 +238,7 @@
     dst += 4 * sizeof(float);
 }
 
-void GradientCache::generateTexture(uint32_t* colors, float* positions,
-        int count, Texture* texture) {
+void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) {
     const uint32_t width = texture->width;
     const GLsizei rowBytes = width * bytesPerPixel();
     uint8_t pixels[rowBytes * texture->height];
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 43934d9..6a783b1 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -151,7 +151,7 @@
     Texture* addLinearGradient(GradientCacheEntry& gradient,
             uint32_t* colors, float* positions, int count);
 
-    void generateTexture(uint32_t* colors, float* positions, int count, Texture* texture);
+    void generateTexture(uint32_t* colors, float* positions, Texture* texture);
 
     struct GradientInfo {
         uint32_t width;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 11e2d33..29030d2 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -779,11 +779,10 @@
 
 int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
         int alpha, SkXfermode::Mode mode, int flags) {
-    const GLuint previousFbo = mSnapshot->fbo;
     const int count = saveSnapshot(flags);
 
     if (!mSnapshot->isIgnored()) {
-        createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo);
+        createLayer(left, top, right, bottom, alpha, mode, flags);
     }
 
     return count;
@@ -835,7 +834,6 @@
 
 int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float bottom,
         int alpha, SkXfermode::Mode mode, int flags) {
-    const GLuint previousFbo = mSnapshot->fbo;
     const int count = saveSnapshot(flags);
 
     if (!mSnapshot->isIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
@@ -911,7 +909,7 @@
  *     something actually gets drawn are the layers regions cleared.
  */
 bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom,
-        int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo) {
+        int alpha, SkXfermode::Mode mode, int flags) {
     LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
     LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
 
@@ -948,7 +946,7 @@
 
     startMark("SaveLayer");
     if (fboLayer) {
-        return createFboLayer(layer, bounds, clip, previousFbo);
+        return createFboLayer(layer, bounds, clip);
     } else {
         // Copy the framebuffer into the layer
         layer->bindTexture();
@@ -974,7 +972,7 @@
     return true;
 }
 
-bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo) {
+bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
     layer->clipRect.set(clip);
     layer->setFbo(mCaches.fboCache.get());
 
@@ -1268,15 +1266,15 @@
         }
 
 #if DEBUG_LAYERS_AS_REGIONS
-        drawRegionRects(layer->region);
+        drawRegionRectsDebug(layer->region);
 #endif
 
         layer->region.clear();
     }
 }
 
-void OpenGLRenderer::drawRegionRects(const Region& region) {
 #if DEBUG_LAYERS_AS_REGIONS
+void OpenGLRenderer::drawRegionRectsDebug(const Region& region) {
     size_t count;
     const android::Rect* rects = region.getArray(&count);
 
@@ -1298,8 +1296,8 @@
         drawColorRect(r.left, r.top, r.right, r.bottom, colors[offset + (i & 0x1)],
                 SkXfermode::kSrcOver_Mode);
     }
-#endif
 }
+#endif
 
 void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
         SkXfermode::Mode mode, bool dirty) {
@@ -1792,7 +1790,7 @@
     mColorG = mColorA * ((color >>  8) & 0xFF) / 255.0f;
     mColorB = mColorA * ((color      ) & 0xFF) / 255.0f;
     mColorSet = true;
-    mSetShaderColor = mDescription.setColor(mColorR, mColorG, mColorB, mColorA);
+    mSetShaderColor = mDescription.setColorModulate(mColorA);
 }
 
 void OpenGLRenderer::setupDrawAlpha8Color(int color, int alpha) {
@@ -1801,7 +1799,7 @@
     mColorG = mColorA * ((color >>  8) & 0xFF) / 255.0f;
     mColorB = mColorA * ((color      ) & 0xFF) / 255.0f;
     mColorSet = true;
-    mSetShaderColor = mDescription.setAlpha8Color(mColorR, mColorG, mColorB, mColorA);
+    mSetShaderColor = mDescription.setAlpha8ColorModulate(mColorR, mColorG, mColorB, mColorA);
 }
 
 void OpenGLRenderer::setupDrawTextGamma(const SkPaint* paint) {
@@ -1814,7 +1812,7 @@
     mColorG = g;
     mColorB = b;
     mColorSet = true;
-    mSetShaderColor = mDescription.setColor(r, g, b, a);
+    mSetShaderColor = mDescription.setColorModulate(a);
 }
 
 void OpenGLRenderer::setupDrawShader() {
@@ -2998,7 +2996,7 @@
         dirtyLayerUnchecked(layerBounds, getRegion());
     }
 
-    drawTextDecorations(text, bytesCount, totalAdvance, oldX, oldY, paint);
+    drawTextDecorations(totalAdvance, oldX, oldY, paint);
 
     return DrawGlInfo::kStatusDrew;
 }
@@ -3132,7 +3130,7 @@
             }
 
 #if DEBUG_LAYERS_AS_REGIONS
-            drawRegionRects(layer->region);
+            drawRegionRectsDebug(layer->region);
 #endif
         }
 
@@ -3272,8 +3270,7 @@
 #define kStdUnderline_Offset    (1.0f / 9.0f)
 #define kStdUnderline_Thickness (1.0f / 18.0f)
 
-void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float underlineWidth,
-        float x, float y, SkPaint* paint) {
+void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y, SkPaint* paint) {
     // Handle underline and strike-through
     uint32_t flags = paint->getFlags();
     if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9afb7ad..6e9c747 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -654,12 +654,11 @@
      * @param alpha The translucency of the layer
      * @param mode The blending mode of the layer
      * @param flags The layer save flags
-     * @param previousFbo The name of the current framebuffer
      *
      * @return True if the layer was successfully created, false otherwise
      */
     bool createLayer(float left, float top, float right, float bottom,
-            int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
+            int alpha, SkXfermode::Mode mode, int flags);
 
     /**
      * Creates a new layer stored in the specified snapshot as an FBO.
@@ -667,9 +666,8 @@
      * @param layer The layer to store as an FBO
      * @param snapshot The snapshot associated with the new layer
      * @param bounds The bounds of the layer
-     * @param previousFbo The name of the current framebuffer
      */
-    bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo);
+    bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip);
 
     /**
      * Compose the specified layer as a region.
@@ -868,8 +866,7 @@
      * @param y The y coordinate where the text will be drawn
      * @param paint The paint to draw the text with
      */
-    void drawTextDecorations(const char* text, int bytesCount, float totalAdvance,
-            float x, float y, SkPaint* paint);
+    void drawTextDecorations(float totalAdvance, float x, float y, SkPaint* paint);
 
    /**
      * Draws shadow layer on text (with optional positions).
@@ -1004,11 +1001,13 @@
     void updateLayers();
     void flushLayers();
 
+#if DEBUG_LAYERS_AS_REGIONS
     /**
      * Renders the specified region as a series of rectangles. This method
      * is used for debugging only.
      */
-    void drawRegionRects(const Region& region);
+    void drawRegionRectsDebug(const Region& region);
+#endif
 
     /**
      * Renders the specified region as a series of rectangles. The region
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 4f94afc..bc0f211 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -207,7 +207,7 @@
      * the fragment shader. When this method returns true, the program should
      * be provided with a modulation color.
      */
-    bool setColor(const float r, const float g, const float b, const float a) {
+    bool setColorModulate(const float a) {
         modulate = a < COLOR_COMPONENT_THRESHOLD;
         return modulate;
     }
@@ -217,7 +217,7 @@
      * the fragment shader. When this method returns true, the program should
      * be provided with a modulation color.
      */
-    bool setAlpha8Color(const float r, const float g, const float b, const float a) {
+    bool setAlpha8ColorModulate(const float r, const float g, const float b, const float a) {
         modulate = a < COLOR_COMPONENT_THRESHOLD || r > COLOR_COMPONENT_INV_THRESHOLD ||
                 g > COLOR_COMPONENT_INV_THRESHOLD || b > COLOR_COMPONENT_INV_THRESHOLD;
         return modulate;
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 797ed10..4f2a432 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -142,7 +142,7 @@
 }
 
 void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
-        const Snapshot& snapshot, GLuint* textureUnit) {
+        const Snapshot&, GLuint* textureUnit) {
     GLuint textureSlot = (*textureUnit)++;
     Caches::getInstance().activeTexture(textureSlot);
 
@@ -228,7 +228,7 @@
 }
 
 void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView,
-        const Snapshot& snapshot, GLuint* textureUnit) {
+        const Snapshot&, GLuint* textureUnit) {
     if (CC_UNLIKELY(!mIsSimple)) {
         GLuint textureSlot = (*textureUnit)++;
         Caches::getInstance().activeTexture(textureSlot);
@@ -264,7 +264,7 @@
 SkiaCircularGradientShader::SkiaCircularGradientShader(float x, float y, float radius,
         uint32_t* colors, float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
         SkMatrix* matrix, bool blend):
-        SkiaSweepGradientShader(kCircularGradient, x, y, colors, positions, count, key,
+        SkiaSweepGradientShader(kCircularGradient, colors, positions, count, key,
                 tileMode, matrix, blend) {
     SkMatrix unitMatrix;
     toCircularUnitMatrix(x, y, radius, &unitMatrix);
@@ -314,11 +314,12 @@
     mIsSimple = count == 2;
 }
 
-SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors,
+SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, uint32_t* colors,
         float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
         SkMatrix* matrix, bool blend):
         SkiaShader(type, key, tileMode, tileMode, matrix, blend),
         mColors(colors), mPositions(positions), mCount(count) {
+    // protected method, that doesn't setup mUnitMatrix - should be handled by subclass
 
     mIsSimple = count == 2 && tileMode == SkShader::kClamp_TileMode;
 }
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index a63431c..9fc99a4 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -192,7 +192,7 @@
             GLuint* textureUnit);
 
 protected:
-    SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
+    SkiaSweepGradientShader(Type type, uint32_t* colors, float* positions,
             int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
     SkiaSweepGradientShader() {
     }
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 0b2c130..6f27b36 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -147,7 +147,7 @@
 // Callbacks
 ///////////////////////////////////////////////////////////////////////////////
 
-void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) {
+void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) {
     if (texture) {
         mSize -= texture->bitmapSize;
 
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index ed0a79a..a56eb33 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -103,7 +103,7 @@
 // Callbacks
 ///////////////////////////////////////////////////////////////////////////////
 
-void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
+void TextureCache::operator()(SkBitmap*&, Texture*& texture) {
     // This will be called already locked
     if (texture) {
         mSize -= texture->bitmapSize;
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index d5f38b5..24ffb80 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -114,7 +114,7 @@
             mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
             mCaches(Caches::getInstance()) {
     mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
-            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
 
     // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
     // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
@@ -143,7 +143,7 @@
     // reset, then create a new remainder space to start again
     reset();
     mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
-            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
 }
 
 void CacheTexture::releaseMesh() {
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 61b38f8..4cc4f22 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -54,7 +54,7 @@
     CacheBlock* mNext;
     CacheBlock* mPrev;
 
-    CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false):
+    CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height):
             mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) {
     }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 680e73a..d652cae 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1572,6 +1572,24 @@
 
     /**
      * @hide
+     * Checks whether any local or remote media playback is active.
+     * Local playback refers to playback for instance on the device's speakers or wired headphones.
+     * Remote playback refers to playback for instance on a wireless display mirroring the
+     *    devices's, or on a device using a Cast-like protocol.
+     * @return true if media playback, from which the device is aware, is active.
+     */
+    public boolean isLocalOrRemoteMusicActive() {
+        IAudioService service = getService();
+        try {
+            return service.isLocalOrRemoteMusicActive();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in isLocalOrRemoteMusicActive()", e);
+            return false;
+        }
+    }
+
+    /**
+     * @hide
      * Checks whether the current audio focus is exclusive.
      * @return true if the top of the audio focus stack requested focus
      *     with {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 79814d1..1f5fefd 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -751,6 +751,26 @@
     ///////////////////////////////////////////////////////////////////////////
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
+    /** @see AudioManager#isLocalOrRemoteMusicActive() */
+    public boolean isLocalOrRemoteMusicActive() {
+        if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+            // local / wired / BT playback active
+            if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): local");
+            return true;
+        }
+        if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+            // remote "cast-like" playback active
+            if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): has PLAYBACK_TYPE_REMOTE");
+            return true;
+        }
+        if (AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, 0)) {
+            // remote submix playback active
+            if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): remote submix");
+            return true;
+        }
+        if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): no");
+        return false;
+    }
 
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustVolume(int direction, int flags, String callingPackage) {
@@ -763,10 +783,10 @@
     public void adjustLocalOrRemoteStreamVolume(int streamType, int direction,
             String callingPackage) {
         if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")");
-        if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
-            mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0);
-        } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+        if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
             adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0, callingPackage);
+        } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
+            mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0);
         }
     }
 
@@ -2612,6 +2632,17 @@
         return (isOffhook || getMode() == AudioManager.MODE_IN_COMMUNICATION);
     }
 
+    /**
+     * For code clarity for getActiveStreamType(int)
+     * @param delay_ms max time since last STREAM_MUSIC activity to consider
+     * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
+     *     in the last "delay_ms" ms.
+     */
+    private boolean isAfMusicActiveRecently(int delay_ms) {
+        return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
+                || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
+    }
+
     private int getActiveStreamType(int suggestedStreamType) {
         if (mVoiceCapable) {
             if (isInCommunication()) {
@@ -2624,23 +2655,22 @@
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
-                // volume can have priority over STREAM_MUSIC
-                if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
-                    return STREAM_REMOTE_MUSIC;
-                } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC,
-                            DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
+                if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
                     if (DEBUG_VOL)
                         Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
                     return AudioSystem.STREAM_MUSIC;
-                } else {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
-                    return AudioSystem.STREAM_RING;
+                } else
+                    if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC))
+                    {
+                        if (DEBUG_VOL)
+                            Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+                        return STREAM_REMOTE_MUSIC;
+                    } else {
+                        if (DEBUG_VOL)
+                            Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
+                        return AudioSystem.STREAM_RING;
                 }
-            } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+            } else if (isAfMusicActiveRecently(0)) {
                 if (DEBUG_VOL)
                     Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
                 return AudioSystem.STREAM_MUSIC;
@@ -2666,14 +2696,17 @@
                 if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
                 return AudioSystem.STREAM_NOTIFICATION;
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) {
-                    // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control
-                    // volume can have priority over STREAM_MUSIC
-                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
-                    return STREAM_REMOTE_MUSIC;
+                if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
+                    return AudioSystem.STREAM_MUSIC;
+                } else
+                    if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC))
+                    {
+                        if (DEBUG_VOL)
+                            Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC");
+                        return STREAM_REMOTE_MUSIC;
                 } else {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
                     return AudioSystem.STREAM_MUSIC;
                 }
             } else {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8c05089..2f08325 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -37,6 +37,8 @@
 
     void adjustVolume(int direction, int flags, String callingPackage);
 
+    boolean isLocalOrRemoteMusicActive();
+
     oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction,
             String callingPackage);
 
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 7865ec8..6dbb3cd 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -812,6 +812,7 @@
         final OnClientUpdateListener l;
         synchronized(mInfoLock) {
             l = mOnClientUpdateListener;
+            mMetadataEditor = null;
         }
         if (l != null) {
             l.onClientChange(clearing);
diff --git a/packages/DocumentsUI/res/values-bg/strings.xml b/packages/DocumentsUI/res/values-bg/strings.xml
index b703541..c3242b2 100644
--- a/packages/DocumentsUI/res/values-bg/strings.xml
+++ b/packages/DocumentsUI/res/values-bg/strings.xml
@@ -44,7 +44,7 @@
     <string name="root_type_shortcut" msgid="3318760609471618093">"Преки пътища"</string>
     <string name="root_type_device" msgid="7121342474653483538">"Устройства"</string>
     <string name="root_type_apps" msgid="8838065367985945189">"Още приложения"</string>
-    <string name="pref_advanced_devices" msgid="903257239609301276">"Показване на устройствата с разширени функции"</string>
+    <string name="pref_advanced_devices" msgid="903257239609301276">"Устройства с разширени ф-ии"</string>
     <string name="pref_file_size" msgid="2826879315743961459">"Показване на файловия размер"</string>
     <string name="pref_device_size" msgid="3542106883278997222">"Показване на размера на устройството"</string>
     <string name="empty" msgid="7858882803708117596">"Няма елементи"</string>
diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml
index a581e08..19d2ebe 100644
--- a/packages/DocumentsUI/res/values-sw720dp/styles.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/styles.xml
@@ -20,5 +20,6 @@
         <item name="android:windowBackground">@*android:drawable/dialog_full_holo_light</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@*android:style/Animation.Holo.Dialog</item>
     </style>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 7660779..d675e8d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -292,7 +292,7 @@
         @Override
         protected Void doInBackground(Void... params) {
             // Restore last stack for calling package
-            final String packageName = getCallingPackage();
+            final String packageName = getCallingPackageMaybeExtra();
             final Cursor cursor = getContentResolver()
                     .query(RecentsProvider.buildResume(packageName), null, null, null, null);
             try {
@@ -783,6 +783,11 @@
         return mState.stack.peek();
     }
 
+    private String getCallingPackageMaybeExtra() {
+        final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
+        return (extra != null) ? extra : getCallingPackage();
+    }
+
     public Executor getCurrentExecutor() {
         final DocumentInfo cwd = getCurrentDirectory();
         if (cwd != null && cwd.authority != null) {
@@ -921,7 +926,7 @@
         if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
 
             // Remember that we last picked via external app
-            final String packageName = getCallingPackage();
+            final String packageName = getCallingPackageMaybeExtra();
             final ContentValues values = new ContentValues();
             values.put(ResumeColumns.EXTERNAL, 1);
             getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
@@ -1002,7 +1007,7 @@
         }
 
         // Remember location for next app launch
-        final String packageName = getCallingPackage();
+        final String packageName = getCallingPackageMaybeExtra();
         values.clear();
         values.put(ResumeColumns.STACK, rawStack);
         values.put(ResumeColumns.EXTERNAL, 0);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index eb56765..b98e1ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -194,16 +194,6 @@
                 handleDocumentsProvider(info.providerInfo);
             }
 
-            // Pick up legacy providers
-            final List<ProviderInfo> legacyProviders = pm.queryContentProviders(
-                    null, -1, PackageManager.GET_META_DATA);
-            for (ProviderInfo info : legacyProviders) {
-                if (info.metaData != null && info.metaData.containsKey(
-                        DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
-                    handleDocumentsProvider(info);
-                }
-            }
-
             final long delta = SystemClock.elapsedRealtime() - start;
             Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms");
             synchronized (mLock) {
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 99a4260..5169fef 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -16,6 +16,14 @@
             </intent-filter>
         </provider>
 
+        <receiver android:name=".MountReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_MOUNTED" />
+                <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
+                <data android:scheme="file" />
+            </intent-filter>
+        </receiver>
+
         <!-- TODO: find a better place for tests to live -->
         <provider
             android:name=".TestDocumentsProvider"
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 9328b33..d42354f 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,20 +16,25 @@
 
 package com.android.externalstorage;
 
+import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MatrixCursor.RowBuilder;
 import android.graphics.Point;
-import android.media.ExifInterface;
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
+import android.util.Log;
 import android.webkit.MimeTypeMap;
 
+import com.android.internal.annotations.GuardedBy;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
@@ -44,6 +49,8 @@
 public class ExternalStorageProvider extends DocumentsProvider {
     private static final String TAG = "ExternalStorage";
 
+    public static final String AUTHORITY = "com.android.externalstorage.documents";
+
     // docId format: root:path/to/file
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
@@ -63,38 +70,91 @@
         public String docId;
     }
 
+    private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
+
+    private StorageManager mStorageManager;
+
+    private final Object mRootsLock = new Object();
+
+    @GuardedBy("mRootsLock")
     private ArrayList<RootInfo> mRoots;
+    @GuardedBy("mRootsLock")
     private HashMap<String, RootInfo> mIdToRoot;
+    @GuardedBy("mRootsLock")
     private HashMap<String, File> mIdToPath;
 
     @Override
     public boolean onCreate() {
+        mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
+
         mRoots = Lists.newArrayList();
         mIdToRoot = Maps.newHashMap();
         mIdToPath = Maps.newHashMap();
 
-        // TODO: support multiple storage devices
-
-        try {
-            final String rootId = "primary";
-            final File path = Environment.getExternalStorageDirectory();
-            mIdToPath.put(rootId, path);
-
-            final RootInfo root = new RootInfo();
-            root.rootId = rootId;
-            root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
-                    | Root.FLAG_SUPPORTS_SEARCH;
-            root.title = getContext().getString(R.string.root_internal_storage);
-            root.docId = getDocIdForFile(path);
-            mRoots.add(root);
-            mIdToRoot.put(rootId, root);
-        } catch (FileNotFoundException e) {
-            throw new IllegalStateException(e);
-        }
+        updateVolumes();
 
         return true;
     }
 
+    public void updateVolumes() {
+        synchronized (mRootsLock) {
+            updateVolumesLocked();
+        }
+    }
+
+    private void updateVolumesLocked() {
+        mRoots.clear();
+        mIdToPath.clear();
+        mIdToRoot.clear();
+
+        final StorageVolume[] volumes = mStorageManager.getVolumeList();
+        for (StorageVolume volume : volumes) {
+            final boolean mounted = Environment.MEDIA_MOUNTED.equals(volume.getState())
+                    || Environment.MEDIA_MOUNTED_READ_ONLY.equals(volume.getState());
+            if (!mounted) continue;
+
+            final String rootId;
+            if (volume.isPrimary() && volume.isEmulated()) {
+                rootId = ROOT_ID_PRIMARY_EMULATED;
+            } else if (volume.getUuid() != null) {
+                rootId = volume.getUuid();
+            } else {
+                Log.d(TAG, "Missing UUID for " + volume.getPath() + "; skipping");
+                continue;
+            }
+
+            if (mIdToPath.containsKey(rootId)) {
+                Log.w(TAG, "Duplicate UUID " + rootId + "; skipping");
+                continue;
+            }
+
+            try {
+                final File path = volume.getPathFile();
+                mIdToPath.put(rootId, path);
+
+                final RootInfo root = new RootInfo();
+                root.rootId = rootId;
+                root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+                        | Root.FLAG_SUPPORTS_SEARCH;
+                if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
+                    root.title = getContext().getString(R.string.root_internal_storage);
+                } else {
+                    root.title = volume.getUserLabel();
+                }
+                root.docId = getDocIdForFile(path);
+                mRoots.add(root);
+                mIdToRoot.put(rootId, root);
+            } catch (FileNotFoundException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots");
+
+        getContext().getContentResolver()
+                .notifyChange(DocumentsContract.buildRootsUri(AUTHORITY), null, false);
+    }
+
     private static String[] resolveRootProjection(String[] projection) {
         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
     }
@@ -108,11 +168,13 @@
 
         // Find the most-specific root path
         Map.Entry<String, File> mostSpecific = null;
-        for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
-            final String rootPath = root.getValue().getPath();
-            if (path.startsWith(rootPath) && (mostSpecific == null
-                    || rootPath.length() > mostSpecific.getValue().getPath().length())) {
-                mostSpecific = root;
+        synchronized (mRootsLock) {
+            for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
+                final String rootPath = root.getValue().getPath();
+                if (path.startsWith(rootPath) && (mostSpecific == null
+                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
+                    mostSpecific = root;
+                }
             }
         }
 
@@ -138,7 +200,10 @@
         final String tag = docId.substring(0, splitIndex);
         final String path = docId.substring(splitIndex + 1);
 
-        File target = mIdToPath.get(tag);
+        File target;
+        synchronized (mRootsLock) {
+            target = mIdToPath.get(tag);
+        }
         if (target == null) {
             throw new FileNotFoundException("No root for " + tag);
         }
@@ -182,23 +247,30 @@
         row.add(Document.COLUMN_DISPLAY_NAME, displayName);
         row.add(Document.COLUMN_SIZE, file.length());
         row.add(Document.COLUMN_MIME_TYPE, mimeType);
-        row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
         row.add(Document.COLUMN_FLAGS, flags);
+
+        // Only publish dates reasonably after epoch
+        long lastModified = file.lastModified();
+        if (lastModified > 31536000000L) {
+            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
+        }
     }
 
     @Override
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
-        for (String rootId : mIdToPath.keySet()) {
-            final RootInfo root = mIdToRoot.get(rootId);
-            final File path = mIdToPath.get(rootId);
+        synchronized (mRootsLock) {
+            for (String rootId : mIdToPath.keySet()) {
+                final RootInfo root = mIdToRoot.get(rootId);
+                final File path = mIdToPath.get(rootId);
 
-            final RowBuilder row = result.newRow();
-            row.add(Root.COLUMN_ROOT_ID, root.rootId);
-            row.add(Root.COLUMN_FLAGS, root.flags);
-            row.add(Root.COLUMN_TITLE, root.title);
-            row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
-            row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
+                final RowBuilder row = result.newRow();
+                row.add(Root.COLUMN_ROOT_ID, root.rootId);
+                row.add(Root.COLUMN_FLAGS, root.flags);
+                row.add(Root.COLUMN_TITLE, root.title);
+                row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
+                row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
+            }
         }
         return result;
     }
@@ -267,7 +339,11 @@
     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
-        final File parent = mIdToPath.get(rootId);
+
+        final File parent;
+        synchronized (mRootsLock) {
+            parent = mIdToPath.get(rootId);
+        }
 
         final LinkedList<File> pending = new LinkedList<File>();
         pending.add(parent);
@@ -304,19 +380,7 @@
             String documentId, Point sizeHint, CancellationSignal signal)
             throws FileNotFoundException {
         final File file = getFileForDocId(documentId);
-        final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
-                file, ParcelFileDescriptor.MODE_READ_ONLY);
-
-        try {
-            final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
-            final long[] thumb = exif.getThumbnailRange();
-            if (thumb != null) {
-                return new AssetFileDescriptor(pfd, thumb[0], thumb[1]);
-            }
-        } catch (IOException e) {
-        }
-
-        return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+        return DocumentsContract.openImageThumbnail(file);
     }
 
     private static String getTypeForFile(File file) {
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
new file mode 100644
index 0000000..8a6c7d6
--- /dev/null
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.externalstorage;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+
+public class MountReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final ContentProviderClient client = context.getContentResolver()
+                .acquireContentProviderClient(ExternalStorageProvider.AUTHORITY);
+        try {
+            ((ExternalStorageProvider) client.getLocalContentProvider()).updateVolumes();
+        } finally {
+            ContentProviderClient.releaseQuietly(client);
+        }
+    }
+}
diff --git a/packages/FusedLocation/res/values-af/strings.xml b/packages/FusedLocation/res/values-af/strings.xml
new file mode 100644
index 0000000..a321abe
--- /dev/null
+++ b/packages/FusedLocation/res/values-af/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Saamgesmelte ligging"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-am/strings.xml b/packages/FusedLocation/res/values-am/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-am/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ar/strings.xml b/packages/FusedLocation/res/values-ar/strings.xml
new file mode 100644
index 0000000..dfbcf7a
--- /dev/null
+++ b/packages/FusedLocation/res/values-ar/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"الموقع المدمج"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-bg/strings.xml b/packages/FusedLocation/res/values-bg/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-bg/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ca/strings.xml b/packages/FusedLocation/res/values-ca/strings.xml
new file mode 100644
index 0000000..bdd55dd
--- /dev/null
+++ b/packages/FusedLocation/res/values-ca/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Ubicació unificada"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-cs/strings.xml b/packages/FusedLocation/res/values-cs/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-cs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-da/strings.xml b/packages/FusedLocation/res/values-da/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-da/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-de/strings.xml b/packages/FusedLocation/res/values-de/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-de/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-el/strings.xml b/packages/FusedLocation/res/values-el/strings.xml
new file mode 100644
index 0000000..1243b21
--- /dev/null
+++ b/packages/FusedLocation/res/values-el/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Ενοποιημένη τοποθεσία"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-en-rGB/strings.xml b/packages/FusedLocation/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-en-rGB/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-en-rIN/strings.xml b/packages/FusedLocation/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-en-rIN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-es-rUS/strings.xml b/packages/FusedLocation/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..ec95314
--- /dev/null
+++ b/packages/FusedLocation/res/values-es-rUS/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Ubicación combinada"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-es/strings.xml b/packages/FusedLocation/res/values-es/strings.xml
new file mode 100644
index 0000000..ec95314
--- /dev/null
+++ b/packages/FusedLocation/res/values-es/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Ubicación combinada"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-et-rEE/strings.xml b/packages/FusedLocation/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..fcf4ef7
--- /dev/null
+++ b/packages/FusedLocation/res/values-et-rEE/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Liidetud asukoht"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-fa/strings.xml b/packages/FusedLocation/res/values-fa/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-fa/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-fi/strings.xml b/packages/FusedLocation/res/values-fi/strings.xml
new file mode 100644
index 0000000..2d308b3
--- /dev/null
+++ b/packages/FusedLocation/res/values-fi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Yhdistetty sijainti"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-fr-rCA/strings.xml b/packages/FusedLocation/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-fr-rCA/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-fr/strings.xml b/packages/FusedLocation/res/values-fr/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-fr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-hi/strings.xml b/packages/FusedLocation/res/values-hi/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-hi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-hr/strings.xml b/packages/FusedLocation/res/values-hr/strings.xml
new file mode 100644
index 0000000..b2a2cc9
--- /dev/null
+++ b/packages/FusedLocation/res/values-hr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Lokacija iz kombiniranih izvora"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-hu/strings.xml b/packages/FusedLocation/res/values-hu/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-hu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-hy-rAM/strings.xml b/packages/FusedLocation/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-hy-rAM/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-in/strings.xml b/packages/FusedLocation/res/values-in/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-in/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-it/strings.xml b/packages/FusedLocation/res/values-it/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-it/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-iw/strings.xml b/packages/FusedLocation/res/values-iw/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-iw/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ja/strings.xml b/packages/FusedLocation/res/values-ja/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-ja/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ka-rGE/strings.xml b/packages/FusedLocation/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-ka-rGE/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-km-rKH/strings.xml b/packages/FusedLocation/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..649e4f7
--- /dev/null
+++ b/packages/FusedLocation/res/values-km-rKH/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"ភ្ជាប់​ទីតាំង"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ko/strings.xml b/packages/FusedLocation/res/values-ko/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-ko/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-lo-rLA/strings.xml b/packages/FusedLocation/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-lo-rLA/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-lt/strings.xml b/packages/FusedLocation/res/values-lt/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-lt/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-lv/strings.xml b/packages/FusedLocation/res/values-lv/strings.xml
new file mode 100644
index 0000000..8c40cb2
--- /dev/null
+++ b/packages/FusedLocation/res/values-lv/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Apvienota atrašanās vieta"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-mn-rMN/strings.xml b/packages/FusedLocation/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..8055ca8
--- /dev/null
+++ b/packages/FusedLocation/res/values-mn-rMN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Нэгдмэл байршил"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ms-rMY/strings.xml b/packages/FusedLocation/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..d345cb8
--- /dev/null
+++ b/packages/FusedLocation/res/values-ms-rMY/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Lokasi Terlakur"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-nb/strings.xml b/packages/FusedLocation/res/values-nb/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-nb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-nl/strings.xml b/packages/FusedLocation/res/values-nl/strings.xml
new file mode 100644
index 0000000..b3003c8
--- /dev/null
+++ b/packages/FusedLocation/res/values-nl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Samengestelde locatie"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-pl/strings.xml b/packages/FusedLocation/res/values-pl/strings.xml
new file mode 100644
index 0000000..b3a9e2a
--- /dev/null
+++ b/packages/FusedLocation/res/values-pl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Przybliżona lokalizacja"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-pt-rPT/strings.xml b/packages/FusedLocation/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..2707fa9
--- /dev/null
+++ b/packages/FusedLocation/res/values-pt-rPT/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Localização Fundida"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-pt/strings.xml b/packages/FusedLocation/res/values-pt/strings.xml
new file mode 100644
index 0000000..cf5f5bf
--- /dev/null
+++ b/packages/FusedLocation/res/values-pt/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Localização fundida"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ro/strings.xml b/packages/FusedLocation/res/values-ro/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-ro/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-ru/strings.xml b/packages/FusedLocation/res/values-ru/strings.xml
new file mode 100644
index 0000000..40d9e6f
--- /dev/null
+++ b/packages/FusedLocation/res/values-ru/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Геоданные из нескольких источников"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-sk/strings.xml b/packages/FusedLocation/res/values-sk/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-sk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-sl/strings.xml b/packages/FusedLocation/res/values-sl/strings.xml
new file mode 100644
index 0000000..928c50b
--- /dev/null
+++ b/packages/FusedLocation/res/values-sl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Kombinirana lokacija"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-sr/strings.xml b/packages/FusedLocation/res/values-sr/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-sr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-sv/strings.xml b/packages/FusedLocation/res/values-sv/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-sv/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-sw/strings.xml b/packages/FusedLocation/res/values-sw/strings.xml
new file mode 100644
index 0000000..60c235e
--- /dev/null
+++ b/packages/FusedLocation/res/values-sw/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Eneo Hakika"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-th/strings.xml b/packages/FusedLocation/res/values-th/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-th/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-tl/strings.xml b/packages/FusedLocation/res/values-tl/strings.xml
new file mode 100644
index 0000000..94a2507
--- /dev/null
+++ b/packages/FusedLocation/res/values-tl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Naka-fuse na Lokasyon"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-tr/strings.xml b/packages/FusedLocation/res/values-tr/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-tr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-uk/strings.xml b/packages/FusedLocation/res/values-uk/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-uk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-vi/strings.xml b/packages/FusedLocation/res/values-vi/strings.xml
new file mode 100644
index 0000000..e0e83a8
--- /dev/null
+++ b/packages/FusedLocation/res/values-vi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Vị trí được hợp nhất"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-zh-rCN/strings.xml b/packages/FusedLocation/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..e6d7bc1
--- /dev/null
+++ b/packages/FusedLocation/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"一体化位置信息"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-zh-rHK/strings.xml b/packages/FusedLocation/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-zh-rHK/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-zh-rTW/strings.xml b/packages/FusedLocation/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..0d2cccc
--- /dev/null
+++ b/packages/FusedLocation/res/values-zh-rTW/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Fused Location"</string>
+</resources>
diff --git a/packages/FusedLocation/res/values-zu/strings.xml b/packages/FusedLocation/res/values-zu/strings.xml
new file mode 100644
index 0000000..62d4359
--- /dev/null
+++ b/packages/FusedLocation/res/values-zu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="5379477904423203699">"Indawo ehlanganisiwe"</string>
+</resources>
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
index 4ba6c34..7cee066 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java
@@ -131,14 +131,16 @@
     private void enableProvider(String name, long minTime) {
         ProviderStats stats = mStats.get(name);
 
-        if (!stats.requested) {
-            stats.requestTime = SystemClock.elapsedRealtime();
-            stats.requested = true;
-            stats.minTime = minTime;
-            mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
-        } else if (stats.minTime != minTime) {
-            stats.minTime = minTime;
-            mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
+        if (stats.available) {
+            if (!stats.requested) {
+                stats.requestTime = SystemClock.elapsedRealtime();
+                stats.requested = true;
+                stats.minTime = minTime;
+                mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
+            } else if (stats.minTime != minTime) {
+                stats.minTime = minTime;
+                mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper);
+            }
         }
     }
 
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
new file mode 100644
index 0000000..13fcbb4
--- /dev/null
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Invoertoestelle"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-sleutelbord"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engels (VK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engels (VS)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internasionale styl"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak-styl"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak-styl"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russies"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russies, Mac-styl"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spaans"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Switserse Frans"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Switserse Duits"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgies"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaars"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiaans"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Deens"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noors"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Sweeds"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fins"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroaties"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tsjeggies"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estnies"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongaars"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Yslands"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiliaans"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugees"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slowaaks"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Sloweens"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turks"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Oekraïens"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
new file mode 100644
index 0000000..d475772
--- /dev/null
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"የግቤት መሣሪያዎች"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"የAndroid የቁልፍ ሰሌዳ"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"እንግሊዝኛ (ዩኬ)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"እንግሊዘኛ (ዩ.ኤስ.)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ሩስያኛ"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ሩስያኛ፣ Mac ቅጥ"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ስፓኒሽ"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"የስዊዝ ፈረንሳይኛ"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"የስዊዝ ጀርመን"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"ቤልጂየም"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ቡልጋሪያ"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"ጣሊያንኛ"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"ዴኒሽ"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ኖርዌጂያ"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"ስዊድንኛ"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ፊኒሽ"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"ክሮሽያ"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"ቼክ"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"ኤስቶኒያ"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ሀንጋሪ"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"አይስላንድ"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ብራዚል"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"ፖርቹጋል"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"ስሎቫክ"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"ስሎቫኒያ"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ቱርክኛ"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ዩክሬን"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
new file mode 100644
index 0000000..abf0967
--- /dev/null
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"أجهزة إدخال بيانات"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"لوحة مفاتيح Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"الإنجليزية (المملكة المتحدة)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"الإنجليزية (الولايات المتحدة)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"الإنجليزية (الولايات المتحدة)، النمط الدولي"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"الإنجليزية (الولايات المتحدة)، نمط Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"الإنجليزية (الولايات المتحدة)، نمط Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"الألمانية"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"الفرنسية"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"الفرنسية (كندا)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"الروسية"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"الروسية، نمط Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"الإسبانية"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"الفرنسية السويسرية"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"الألمانية السويسرية"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"البلجيكية"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"البلغارية"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"الإيطالية"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"الدانماركية"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"النرويجية"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"السويدية"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"الفنلندية"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"الكرواتية"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"التشيكية"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"الإستونية"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"المجرية"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"الأيسلندية"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"البرازيلية"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"البرتغالية"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"السلوفاكية"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"السلوفينية"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"التركية"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"الأوكرانية"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
new file mode 100644
index 0000000..0c413a4
--- /dev/null
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Входни устройства"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Клавиатура на Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"английски (Великобритания)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"английски (САЩ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англ. (САЩ) – стил „Mеждународна“"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английски (САЩ) – стил „Коулмак“"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английски (САЩ) – стил „Дворак“"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немски"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"френски"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"френски (Канада)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"руски"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"руски – стил „Mac“"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"испански"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"швейцарски френски"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"швейцарски немски"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"белгийски"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"български"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"италиански"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"датски"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвежки"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"шведски"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"финландски"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хърватски"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"чешки"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"естонски"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"унгарски"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"исландски"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"португалски (Бразилия)"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"португалски"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"словашки"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"словенски"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"турски"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"украински"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml
new file mode 100644
index 0000000..2021b8f
--- /dev/null
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositius d’entrada"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Teclat Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Anglès (RU)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Anglès (EUA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglès (EUA), estil internacional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglès (EUA), estil Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglès (EUA), estil Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemany"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francès"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francès (Canadà)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rus"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rus, estil Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Espanyol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francès suís"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemany suís"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgar"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italià"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danès"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruec"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Suec"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finès"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croat"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Txec"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonià"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongarès"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandès"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiler"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portuguès"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Eslovac"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Eslovè"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turc"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraïnès"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
new file mode 100644
index 0000000..33b420e
--- /dev/null
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Vstupní zařízení"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Klávesnice Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"anglické (Spojené království)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"anglické (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruské"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruské, styl Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španělské"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"švýcarské (francouzština)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švýcarské (němčina)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgické"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulharské"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"italské"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"dánské"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norské"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"švédské"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finské"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"chorvatské"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"české"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonské"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"maďarské"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandské"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazilské"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalské"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovenské"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovinské"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turecké"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinské"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
new file mode 100644
index 0000000..fc07db2
--- /dev/null
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Inputenheder"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-tastatur"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelsk (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engelsk (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), international stil"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russisk"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russisk, Mac-stil"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spansk"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Schweizisk fransk"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Schweizertysk"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisk"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarsk"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiensk"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dansk"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norsk"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svensk"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finsk"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatisk"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tjekkisk"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estisk"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungarsk"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandsk"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiliansk"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugisisk"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakisk"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovensk"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Tyrkisk"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainsk"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
new file mode 100644
index 0000000..b5c3b50
--- /dev/null
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Eingabegeräte"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-Tastatur"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Englisch (Großbritannien)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Englisch (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russisch"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russisch, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanisch"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Schweizer Französisch"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Schweizerdeutsch"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisch"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarisch"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienisch"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dänisch"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegisch"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Schwedisch"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnisch"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatisch"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tschechisch"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estnisch"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungarisch"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Isländisch"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasilianisch"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugiesisch"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slowakisch"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slowenisch"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Türkisch"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainisch"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
new file mode 100644
index 0000000..f5d57a0
--- /dev/null
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Συσκευές εισόδου"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Πληκτρολόγιο Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Αγγλικά (ΗΒ)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Αγγλικά (ΗΠΑ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Ρωσικά"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ρωσικά, τύπου Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Ισπανικά"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Γαλλικά Ελβετίας"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Γερμανικά Ελβετίας"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Βελγικά"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Βουλγαρικά"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Ιταλικά"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Δανικά"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Νορβηγικά"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Σουηδικά"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Φινλανδικά"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Κροατικά"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Τσεχικά"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Εσθονικά"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ουγγρικά"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Ισλανδικά"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Βραζιλιάνικα"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Πορτογαλικά"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Σλοβακικά"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Σλοβενικά"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Τουρκικά"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ουκρανικά"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..2d794a6
--- /dev/null
+++ b/packages/InputDevices/res/values-en-rGB/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android keyboard"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"English (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"English (US)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russian"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russian, Mac style"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanish"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Swiss French"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Swedish"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnish"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croatian"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Czech"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonian"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hungarian"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Icelandic"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazilian"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portuguese"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovak"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenian"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkish"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainian"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..2d794a6
--- /dev/null
+++ b/packages/InputDevices/res/values-en-rIN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android keyboard"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"English (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"English (US)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russian"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russian, Mac style"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanish"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Swiss French"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Swedish"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnish"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croatian"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Czech"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonian"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hungarian"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Icelandic"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazilian"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portuguese"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovak"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenian"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkish"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainian"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..2d61b80
--- /dev/null
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositivos de entrada"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Teclado de Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglés (Reino Unido)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglés (EE. UU.)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Ruso"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ruso, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Español"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francés de Suiza"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemán de Suiza"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danés"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruego"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Sueco"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandés"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croata"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Checo"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonio"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Húngaro"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandés"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasileño"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugués"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Eslovaco"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Esloveno"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turco"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraniano"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml
new file mode 100644
index 0000000..82ea4d6
--- /dev/null
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositivos de entrada"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Teclado de Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglés (Reino Unido)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglés (EE.UU.)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE.UU.), estilo internacional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE.UU.), estilo Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE.UU.), estilo Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Ruso"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ruso, estilo Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Español"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francés de Suiza"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemán suizo"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danés"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruego"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Sueco"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandés"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croata"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Checo"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonio"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Húngaro"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandés"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasileño"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugués"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Eslovaco"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Esloveno"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turco"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraniano"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-et-rEE/strings.xml b/packages/InputDevices/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..5b4fa3b
--- /dev/null
+++ b/packages/InputDevices/res/values-et-rEE/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Sisendseadmed"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Androidi klaviatuur"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglise (Ühendkuningriik)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglise (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglise (USA), rahvusvaheline stiil"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglise (USA), Colemaki stiil"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglise (USA), Dvoraki stiil"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Saksa"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prantsuse"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prantsuse (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Vene"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Vene, Maci stiil"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Hispaania"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Šveitsi prantsuse"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Šveitsisaksa"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgia"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaaria"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Itaalia"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Taani"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norra"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Rootsi"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Soome"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Horvaatia"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tšehhi"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Eesti"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungari"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandi"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiilia"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugali"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovaki"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Sloveenia"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Türgi"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukraina"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
new file mode 100644
index 0000000..d6a842e
--- /dev/null
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"صفحه‌کلید Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"انگلیسی (بریتانیا)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"انگلیسی (امریکا)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بین‌المللی"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگلیسی (ایالات متحده)، سبک Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگلیسی (ایالات متحده)، سبک Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)‏"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"روسی"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"روسی، سبک Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"اسپانیایی"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"فرانسوی سوئیس"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"آلمانی سوئیسی"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"بلژیکی"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"بلغاری"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"ایتالیایی"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"دانمارکی"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"نروژی"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"سوئدی"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"فنلاندی"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"کرواسی"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"چک"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"استونیایی"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"مجارستانی"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ایسلندی"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"برزیلی"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"پرتغالی"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"اسلوواکی"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"اسلوونیایی"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ترکی"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"اوکراینی"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
new file mode 100644
index 0000000..428eb30
--- /dev/null
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Syöttölaitteet"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-näppäimistö"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"englanti (Iso-Britannia)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"englanti (Yhdysvallat)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"venäjä"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"venäjä, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"espanja"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"sveitsinranska"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"sveitsinsaksa"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgialainen"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulgaria"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"italia"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"tanska"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norja"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"ruotsi"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"suomi"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"kroaatti"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"tšekki"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"viro"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"unkari"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islanti"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brasilialainen"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugali"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovakki"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"sloveeni"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turkki"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukraina"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c947634
--- /dev/null
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Périphériques d\'entrée"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Clavier Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Anglais (britannique)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Anglais (États-Unis)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russe"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russe, type Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Espagnol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Français (Suisse)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Suédois"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnois"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croate"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tchèque"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonien"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongrois"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandais"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brésilien"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugais"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovaque"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovène"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turc"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainien"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
new file mode 100644
index 0000000..4ad4ffa
--- /dev/null
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Clavier Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Anglais (Royaume-Uni)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Anglais (États-Unis)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russe"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russe, type Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Espagnol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Français (Suisse)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Suédois"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnois"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croate"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tchèque"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonien"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongrois"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandais"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brésilien"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugais"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovaque"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovène"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turc"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainien"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
new file mode 100644
index 0000000..8e1864e
--- /dev/null
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"इनपुट उपकरण"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android कीबोर्ड"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"अंग्रेज़ी (यूके)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"अंग्रेज़ी (यूएस)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अंग्रेज़ी (यूएस), अंतर्राष्ट्रीय शैली"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अंग्रेज़ी (यूएस), कोलमैक शैली"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अंग्रेज़ी (यूएस), ड्वोरक शैली"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ़्रांसीसी"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ़्रांसीसी (कनाडा)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"रूसी"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"रूसी, मैक शैली"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"स्पैनिश"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"स्विस फ़्रांसीसी"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस जर्मन"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियाई"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बुल्‍गारियाई"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"इतालवी"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"डैनिश"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नार्वेजियाई"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"स्वीडिश"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"फ़िनिश"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"क्रोएशियाई"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"चेक"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"एस्टोनियाई"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"हंगेरियाई"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"आइसलैंडिक"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ब्राज़ीलियाई"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"पुर्तगाली"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"स्लोवाक"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"स्लोवेनियाई"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"तुर्की"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"यूक्रेनियाई"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
new file mode 100644
index 0000000..6217bf0
--- /dev/null
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Uređaji za unos"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android tipkovnica"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"engleska (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engleska (SAD)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruska"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruska, raspored Maca"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španjolska"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"švicarsko-francuska"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarsko-njemačka"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarska"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"talijanska"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"švedska"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"hrvatska"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"češka"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonska"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"mađarska"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandska"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazilska"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalska"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovačka"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenska"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turska"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinska"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml
new file mode 100644
index 0000000..0cdbfb2
--- /dev/null
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Beviteli eszközök"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-billentyűzet"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"angol (Egyesült Királyság)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"angol (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angol (USA), nemzetközi stílus"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angol (USA), Colemak-stílus"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angol (USA), Dvorak-stílus"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"német"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"francia"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francia (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"orosz"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"orosz, Mac-stílus"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"spanyol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"svájci francia"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"svájci német"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bolgár"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"olasz"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"dán"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norvég"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"svéd"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finn"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"horvát"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"cseh"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"észt"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"magyar"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"izlandi"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazil"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugál"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"szlovák"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"szlovén"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"török"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrán"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..bc5bbfc
--- /dev/null
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Մուտքագրման ​​սարքեր"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android ստեղնաշար"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Անգլերեն (ՄԲ)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Անգլերեն (ԱՄՆ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Ռուսերեն"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ռուսերեն, Mac տեսակ"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Իսպաներեն"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Շվեյցարական ֆրանսերեն"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Շվեյցարական գերմաներեն"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Բելգիական"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Բուլղարերեն"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Իտալերեն"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Դանիերեն"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Նորվեգերեն"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Շվեդերեն"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Ֆիններեն"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Խորվաթերեն"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Չեխերեն"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Էստոներեն"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Հունգարերեն"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Իսլանդերեն"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Բրազիլերեն"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Պորտուգալերեն"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Սլովակերեն"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Սլովեներեն"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Թուրքերեն"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ուկրաիներեն"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
new file mode 100644
index 0000000..11e2dd0a
--- /dev/null
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Perangkat Masukan"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Keyboard Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inggris (Britania)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inggris (Amerika)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inggris (AS), gaya Internasional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inggris (AS), gaya Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inggris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Jerman"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancis"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancis (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rusia"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rusia, gaya Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanyol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Prancis Swiss"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Jerman Swiss"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgia"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaria"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italia"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Denmark"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegia"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Swedia"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandia"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroasia"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Cheska"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonia"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hungaria"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandia"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasil"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugis"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slowakia"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenia"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turki"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukraina"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
new file mode 100644
index 0000000..dbb7301
--- /dev/null
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositivi di input"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Tastiera Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglese (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglese (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglese (USA), stile internazionale"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglese (USA), stile Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglese (USA), stile Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tedesco"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francese"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francese (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russo"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russo, stile Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spagnolo"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francese svizzero"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tedesco svizzero"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaro"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danese"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegese"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svedese"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandese"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croato"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Ceco"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estone"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungherese"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandese"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiliano"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portoghese"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovacco"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Sloveno"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turco"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraino"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
new file mode 100644
index 0000000..5fab322
--- /dev/null
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"התקני קלט"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"מקלדת Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"אנגלית (בריטניה)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"אנגלית (ארה\"ב)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"אנגלית (ארה\"ב), סגנון Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"אנגלית (ארה\"ב), סגנון Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"רוסית"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"רוסית, סגנון Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ספרדית"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"צרפתית שוויצרית"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"גרמנית שוויצרית"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"בלגית"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"בולגרית"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"איטלקית"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"דנית"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"נורווגית"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"שוודית"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"פינית"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"קרואטית"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"צ\'כית"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"אסטונית"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"הונגרית"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"איסלנדית"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ברזילאית"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"פורטוגזית"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"סלובקית"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"סלובנית"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"טורקית"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"אוקראינית"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
new file mode 100644
index 0000000..950b727
--- /dev/null
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Androidキーボード"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"英語(イギリス)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"英語(アメリカ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ロシア語"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ロシア語(Mac配列)"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"スペイン語"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"フランス語(スイス)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ドイツ語(スイス)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"ベルギー語"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ブルガリア語"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"イタリア語"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"デンマーク語"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ノルウェー語"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"スウェーデン語"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"フィンランド語"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"クロアチア語"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"チェコ語"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"エストニア語"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ハンガリー語"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"アイスランド語"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ブラジル配列"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"ポルトガル語"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"スロバキア語"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"スロベニア語"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"トルコ語"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ウクライナ語"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..6e507aa
--- /dev/null
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"შეყვანის მოწყობილობები"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-ის კლავიატურა"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"ინგლისური (გართ. სამ.)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"ინგლისური (აშშ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"რუსული"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"რუსული, Mac სტილი"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ესპანური"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"შვეიცარიული ფრანგული"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"შვეიცარიული გერმანული"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"ბელგიური"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ბულგარული"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"იტალიური"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"დანიური"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ნორვეგიული"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"შვედური"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ფინური"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"ხორვატიული"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"ჩეხური"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"ესტონური"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"უნგრული"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ისლანდიური"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ბრაზილიური"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"პორტუგალიური"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"სლოვაკური"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"სლოვენური"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"თურქული"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"უკრაინული"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-km-rKH/strings.xml b/packages/InputDevices/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..9a8c99b
--- /dev/null
+++ b/packages/InputDevices/res/values-km-rKH/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"ឧបករណ៍​បញ្ចូល"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"ក្ដារចុច​ Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"អង់គ្លេស (​ចក្រភព​អង់គ្លេស)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"អង់គ្លេស (​សហរដ្ឋ​អាមេរិក)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម​​អន្តរជាតិ"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"អាល្លឺម៉ង់"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"បារាំង"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"បារាំង (កាណាដា​)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"រុស្សី"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"រុស្សី ​រចនាប័ទ្ម Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"អេស្ប៉ាញ"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"​​បារាំង​ ស្វីស"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"អាល្លឺម៉ង់ ស្វីស"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"បែលហ្ស៊ិក"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ប៊ុលហ្ការី"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"អ៊ីតាលី"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"ដាណឺម៉ាក"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ន័រវែស"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"ស៊ុយអែដ"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ហ្វាំងឡង់"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"ក្រូអាត"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"ឆេក"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"អេស្តូនី"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ហុងគ្រី"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"អ៊ីស្លង់"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ប្រេស៊ីល"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"ព័រទុយហ្គាល់"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"ស្លូវ៉ាគី"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"ស្លូវ៉ានី"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ទួរគី"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"អ៊ុយក្រែន"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
new file mode 100644
index 0000000..8071586
--- /dev/null
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"입력 기기"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android 키보드"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"영어(영국)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"영어(미국)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"러시아어"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"러시아어, Mac 스타일"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"스페인어"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"프랑스어(스위스)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"독일어(스위스)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"벨기에어"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"불가리아어"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"이탈리아어"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"덴마크어"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"노르웨이어"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"스웨덴어"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"핀란드어"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"크로아티아어"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"체코어"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"에스토니아어"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"헝가리어"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"아이슬란드어"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"브라질어"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"포르투갈어"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"슬로바키아어"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"슬로베니아어"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"터키어"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"우크라이나어"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..2c97e11
--- /dev/null
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"ອຸປະກອນປ້ອນຂໍ້ມູນ"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"ແປ້ນພິມ Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"ອັງກິດ (ສະຫະລັດຊະອານາຈັກ)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"ອັງກິດ (ສະຫະລັດຯ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ຣັດເຊຍ"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ຣັດຊຽຍ, ແບບ Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"ສະແປນນິດ"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"ສະວິສ ຝຣັ່ງ"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ສະວິສ ເຢຍລະມັນ"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"ເບວຢ້ຽນ"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ຮັງກາຣຽນ"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"ອິຕາລຽນ"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"ເດັນນິຊ"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ນໍເວກຽນ"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"ສະວີດິຊ"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ຟິນນິຊ"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"ໂຄຣເອທຽນ"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"ເຊກ"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"ເອສໂຕນຽນ"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ຮັງກາຣຽນ"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ໄອສແລນດິກ"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"ບຣາຊິລຽນ"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"ປໍຕູກີສ"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"ສະໂລແວັກ"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"ສະໂລເວນຽນ"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ເຕີກິສ"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ຢູເຄຣນຽນ"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml
new file mode 100644
index 0000000..c0ed159
--- /dev/null
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"„Android“ klaviatūra"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Anglų k. (JK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Anglų k. (JAV)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglų k. (JAV), tarptautinis stilius"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglų k. (JAV), „Colemak“ stilius"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglų k. (JAV), „Dvorak“ stilius"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vokiečių k."</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancūzų k."</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancūzų k. (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rusų k."</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rusų k., „Mac“ stilius"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Ispanų k."</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Šveicarijos prancūzų k."</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Šveicarijos vokiečių k."</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgų k."</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarų k."</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italų k."</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danų k."</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegų k."</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Švedų k."</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Suomių k."</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatų k."</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Čekų k."</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estų k."</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Vengrų k."</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandų k."</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazilų k."</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugalų k."</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakų k."</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovėnų k."</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkų k."</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainiečių k."</string>
+</resources>
diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml
new file mode 100644
index 0000000..07a8654
--- /dev/null
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Ievadierīces"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android tastatūra"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Angļu (Lielbritānija)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Angļu (ASV)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angļu (ASV), starptautiska"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angļu (ASV), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angļu (ASV), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vācu"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franču"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franču (Kanāda)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Krievu"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Krievu, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spāņu"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Franču (Šveice)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Vācu (Šveice)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Beļģu"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgāru"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Itāļu"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dāņu"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvēģu"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Zviedru"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Somu"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Horvātu"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Čehu"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Igauņu"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungāru"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Īslandiešu"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazīļu"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugāļu"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovāku"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovēņu"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turku"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukraiņu"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-mn-rMN/strings.xml b/packages/InputDevices/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..0cfb085
--- /dev/null
+++ b/packages/InputDevices/res/values-mn-rMN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Оруулах Төхөөрөмж"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Андройд гар"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Англи (ИБ)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Англи (АНУ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англи (АНУ), Олон улсын стиль"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англи (АНУ), Колемак стиль"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англи (АНУ), Дворак стиль"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Герман"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Франц"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Франц (Канад)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Орос"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Орос, Maк стиль"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Испани"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Швейцарийн Франц"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Швейцарийн Герман"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельги"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгар"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Итали"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Дани"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвеги"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Швед"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Финлянд"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Хорват"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Чех"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Эстони"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Унгар"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Исланд"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Бразил"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Португал"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Словак"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Словени"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Турк"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Украйн"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ms-rMY/strings.xml b/packages/InputDevices/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..486f048
--- /dev/null
+++ b/packages/InputDevices/res/values-ms-rMY/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Peranti Input"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Papan kekunci Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Bahasa Inggeris (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Bahasa Inggeris (AS)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Bahasa Inggeris (AS), gaya A/bangsa"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Bahasa Inggeris (AS), gaya Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Bahasa Inggeris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Bahasa Jerman"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Bahasa Perancis"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Bahasa Perancis (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Bahasa Rusia"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Bahasa Rusia, gaya Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Bahasa Sepanyol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Perancis Switzerland"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Jerman Switzerland"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Bahasa Belgium"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bahasa Bulgaria"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Bahasa Itali"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Bahasa Denmark"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Bahasa Norway"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Bahasa Sweden"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Bahasa Finland"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Bahasa Croatia"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Bahasa Czech"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Bahasa Estonia"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Bahasa Hungary"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Bahasa Iceland"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Bahasa Brazil"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Bahasa Portugis"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Bahasa Slovakia"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Bahasa Slovenia"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Bahasa Turki"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Bahasa Ukraine"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
new file mode 100644
index 0000000..b646061
--- /dev/null
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Inndataenheter"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-tastatur"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelsk (Storbritannia)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engelsk (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russisk"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russisk, Mac-stil"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spansk"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Sveitsisk fransk"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Sveitsisk standardtysk"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisk"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarsk"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiensk"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dansk"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norsk"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svensk"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finsk"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatisk"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tsjekkisk"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estisk"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungarsk"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandsk"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasiliansk"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugisisk"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakisk"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovensk"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Tyrkisk"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainsk"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
new file mode 100644
index 0000000..56d84c9
--- /dev/null
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Invoerapparaten"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-toetsenbord"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engels (Verenigd Koninkrijk)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engels (Verenigde Staten)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internationaal"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russisch"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russisch, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spaans"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Zwitsers Frans"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Zwitsers Duits"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisch"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaars"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiaans"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Deens"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noors"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Zweeds"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fins"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatisch"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tsjechisch"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estlands"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hongaars"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"IJslands"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Braziliaans"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugees"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slowaaks"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Sloveens"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turks"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Oekraïens"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
new file mode 100644
index 0000000..4522215
--- /dev/null
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Urządzenia wejściowe"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Klawiatura Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Angielski (Wielka Brytania)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Angielski (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rosyjski"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rosyjski, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Hiszpański"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francuski (Szwajcaria)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Niemiecki (Szwajcaria)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgijski"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bułgarski"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Włoski"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Duński"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norweski"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Szwedzki"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fiński"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Chorwacki"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Czeski"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estoński"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Węgierski"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandzki"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazylijski"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugalski"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Słowacki"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Słoweński"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turecki"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukraiński"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..9a639cd
--- /dev/null
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositivos de entrada"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Teclado do Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglês (RU)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglês (EUA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), est. Internacional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russo"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russo, estilo Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Espanhol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francês (Suíça)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemão (Suíça)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarquês"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norueguês"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Sueco"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandês"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croata"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Checo"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estónio"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Húngaro"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandês"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasileiro"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Português"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Eslovaco"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Esloveno"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turco"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraniano"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
new file mode 100644
index 0000000..05a0cd0
--- /dev/null
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispositivos de entrada"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Teclado do Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Inglês (Reino Unido)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Inglês (EUA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russo"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russo, estilo Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Espanhol"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Francês suíço"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemão suíço"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarquês"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norueguês"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Sueco"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandês"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croata"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tcheco"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estoniano"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Húngaro"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandês"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brasileiro"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Português"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Eslovaco"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Esloveno"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turco"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraniano"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
new file mode 100644
index 0000000..895d8f6
--- /dev/null
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Dispozitive de introducere de date"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Tastatură Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engleză (Regatul Unit)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engleză (SUA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rusă"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rusă, stil Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spaniolă"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Franceză (Elveția)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Germană (Elveția)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiană"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgară"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiană"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Daneză"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegiană"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Suedeză"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finlandeză"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croată"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Cehă"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estoniană"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Maghiară"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Islandeză"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Braziliană"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugheză"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovacă"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenă"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turcă"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ucraineană"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
new file mode 100644
index 0000000..a4cbfd7
--- /dev/null
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Устройства ввода"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Клавиатура Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"английский (Великобритания)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"английский (США)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"русский"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"русский (Mac)"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"испанский"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"французский (Швейцария)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"немецкий (Швейцария)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"нидерландский (Бельгия)"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"болгарский"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"итальянский"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"датский"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвежский"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"шведский"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"финский"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хорватский"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"чешский"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"эстонский"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"венгерский"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"исландский"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"португальский (Бразилия)"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"португальский"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"словацкий"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"словенский"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"турецкий"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"украинский"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml
new file mode 100644
index 0000000..01ab042
--- /dev/null
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Vstupné zariadenia"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Klávesnica Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"anglické (Spojené kráľovstvo)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"anglické (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), medzinárodné"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), štýl Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), štýl Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemecké"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"francúzske"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francúzske (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruské"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruské, štýl Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španielske"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"švajčiarske (francúzština)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švajčiarske (nemčina)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgické"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulharské"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"talianske"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"dánske"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"nórske"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"švédske"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"fínske"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"chorvátske"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"české"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estónske"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"maďarské"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandské"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazílske"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalské"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovenské"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovinské"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turecké"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinské"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
new file mode 100644
index 0000000..30ff3c4
--- /dev/null
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Vhodne naprave"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Tipkovnica Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"angleška (Združeno kraljestvo)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"angleška (ZDA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"ruska"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"ruska, slog Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"španska"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"švicarska francoska"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarska nemška"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bolgarska"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"italijanska"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"švedska"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"finska"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"hrvaška"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"češka"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"estonska"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"madžarska"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"islandska"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"brazilska"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"portugalska"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"slovaška"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"slovenska"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"turška"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ukrajinska"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
new file mode 100644
index 0000000..d23ac00
--- /dev/null
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Улазни уређаји"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android тастатура"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"енглеска (УК)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"енглеска (САД)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"руска"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"руска, Mac стил"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"шпанска"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"швајцарско француска"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"швајцарско немачка"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"белгијска"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"бугарска"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"италијанска"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"данска"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвешка"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"шведска"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"финска"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хрватска"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"чешка"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"естонска"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"мађарска"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"исландска"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"бразилска"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"португалска"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"словачка"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"словеначка"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"турска"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"украјинска"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
new file mode 100644
index 0000000..25a5ae8
--- /dev/null
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Indataenheter"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Androids tangentbord"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelskt (Storbritannien)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Engelskt (USA)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Ryskt"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Ryskt, Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanskt"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Franskt (Schweiz)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tyskt (Schweiz)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiskt"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgariskt"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienskt"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danskt"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norskt"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Svenskt"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finskt"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kroatiskt"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tjeckiskt"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estniskt"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Ungerskt"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Isländskt"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Portugisiskt (Brasilien)"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portugisiskt"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakiskt"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenskt"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkiskt"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainskt"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
new file mode 100644
index 0000000..65ab96a1
--- /dev/null
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Vifaa Ingizi"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Kibodi ya Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Kiingereza (Uingereza)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Kiingereza (Marekani)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Kirusi"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Kirusi, Muundo wa Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Kihispania"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Kifaransa cha Uswisi"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Kijerumani cha Uswisi"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Kibelgiji"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Kibulgaria"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Kiitaliano"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Kidenmarki"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Kinorwei"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Kiswidi"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Kifinlandi"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Kikroeshia"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Kicheki"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Kiestonia"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Kihungari"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Kiaislandi"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Kibrazili"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Kireno"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Kislovakia"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Kislovenia"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Kituruki"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Kiukrania"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
new file mode 100644
index 0000000..0cc7d47
--- /dev/null
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"อุปกรณ์อินพุต"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"แป้นพิมพ์แอนดรอยด์"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"อังกฤษ (สหราชอาณาจักร)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"อังกฤษ (อเมริกัน)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"อังกฤษ (อเมริกัน), รูปแบบนานาชาติ"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"อังกฤษ (อเมริกัน), รูปแบบ Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"อังกฤษ (อเมริกัน), รูปแบบ Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"เยอรมัน"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"ฝรั่งเศส"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ฝรั่งเศส (แคนาดา)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"รัสเซีย"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"รัสเซีย, รูปแบบ Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"สเปน"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"ฝรั่งเศส (สวิส)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"เยอรมันสวิส"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"เบลเยียม"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"บัลแกเรีย"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"อิตาลี"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"เดนมาร์ก"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"นอร์เวย์"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"สวีเดน"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"ฟินแลนด์"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"โครเอเชีย"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"เช็ก"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"เอสโตเนีย"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"ฮังการี"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ไอซ์แลนดิก"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"บราซิล"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"โปรตุเกส"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"สโลวัก"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"สโลวีเนีย"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"ตุรกี"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"ยูเครน"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml
new file mode 100644
index 0000000..08f34d2
--- /dev/null
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Mga Input Device"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android keyboard"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Ingles (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Ingles (US)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ingles (US), istilong International"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ingles (US), istilong Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ingles (US), istilong Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Russian"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Russian, istilong Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Spanish"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Swiss French"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Swedish"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Finnish"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Croatian"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Czech"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonian"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Hungarian"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Icelandic"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brazilian"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portuguese"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovak"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovenian"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Turkish"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukrainian"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
new file mode 100644
index 0000000..f7c1262
--- /dev/null
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Input Devices"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android klavyesi"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"İngilizce (İngiltere)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"İngilizce (ABD)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Rusça"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Rusça, Mac stili"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"İspanyolca"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"İsviçre Fransızcası"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"İsviçre Almancası"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belçika dili"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarca"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"İtalyanca"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Danca"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norveççe"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"İsveççe"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Fince"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Hırvatça"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Çekçe"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Estonca"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Macarca"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"İzlandaca"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Brezilya dili"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Portekizce"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Slovakça"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Slovence"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Türkçe"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Ukraynaca"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
new file mode 100644
index 0000000..ee6ffc7
--- /dev/null
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Пристрої вводу"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Клавіатура Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"англійська (Великобританія)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"англійська (США)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англійська (США), міжнародна"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"англійська (США), розкладка Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"англійська (США), розкладка Дворака"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"німецька"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"французька"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французька (Канада)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"російська"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"російська, розкладка Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"іспанська"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"французька (Швейцарія)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"німецька (Швейцарія)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"бельгійська"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"болгарська"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"італійська"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"данська"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвезька"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"шведська"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"фінська"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хорватська"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"чеська"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"естонська"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"угорська"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ісландська"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"бразильська"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"португальська"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"словацька"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"словенська"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"турецька"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"українська"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
new file mode 100644
index 0000000..7a65e45
--- /dev/null
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Thiết bị đầu vào"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Bàn phím Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Tiếng Anh (Anh)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Tiếng Anh (Mỹ)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Tiếng Anh (Mỹ), kiểu Quốc tế"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Tiếng Anh (Mỹ), kiểu Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Tiếng Anh (Mỹ), kiểu Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tiếng Đức"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Tiếng Pháp"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Tiếng Pháp (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Tiếng Nga"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Tiếng Nga, kiểu Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Tiếng Tây Ban Nha"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Tiếng Pháp ở Thụy Sĩ"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tiếng Đức Thụy Sĩ"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Tiếng Bỉ"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Tiếng Bungary"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Tiếng Ý"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Tiếng Đan Mạch"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Tiếng Na Uy"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Tiếng Thụy Điển"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Tiếng Phần Lan"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Tiếng Croatia"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Tiếng Séc"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Tiếng Estonia"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Tiếng Hungary"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Tiếng Ai-xơ-len"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Tiếng Brazil"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Tiếng Bồ Đào Nha"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Tiếng Slovak"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Tiếng Sloven"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Tiếng Thổ Nhĩ Kỳ"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Tiếng Ukraina"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..85b1c84
--- /dev/null
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"输入设备"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android 键盘"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"英语(英国)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"英语(美国)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"俄语"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"俄语,Mac 风格"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"西班牙语"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"瑞士法语"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"瑞士德语"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利时语"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亚语"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"意大利语"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麦语"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威语"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"瑞典语"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"芬兰语"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"克罗地亚语"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"捷克语"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"爱沙尼亚语"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"匈牙利语"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"冰岛语"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"巴西语"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"葡萄牙语"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"斯洛伐克语"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"斯洛文尼亚语"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"土耳其语"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"乌克兰语"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..839c546
--- /dev/null
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"輸入裝置"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android 鍵盤"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"英文 (英國)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"英文 (美國)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"俄文"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"俄文,Mac 樣式"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"西班牙文"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"法文 (瑞士)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"德文(瑞士)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利時文"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亞文"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"意大利文"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麥文"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威文"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"瑞典文"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"芬蘭文"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"克羅地亞文"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"捷克文"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"愛沙尼亞文"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"匈牙利文"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"冰島文"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"巴西文"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"葡萄牙文"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"斯洛伐克文"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"斯洛文尼亞文"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"土耳其文"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"烏克蘭文"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ba9f132
--- /dev/null
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"輸入裝置"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android 鍵盤"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"英文 (英國)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"英文 (美國)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"俄文"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"俄文,Mac 樣式"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"西班牙文"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"法文 (瑞士)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"德文 (瑞士)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利時式"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亞文"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"義大利文"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麥文"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威文"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"瑞典文"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"芬蘭文"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"克羅埃西亞文"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"捷克文"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"愛沙尼亞文"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"匈牙利文"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"冰島文"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"巴西式"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"葡萄牙文"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"斯洛伐克文"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"斯洛維尼亞文"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"土耳其文"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"烏克蘭文"</string>
+</resources>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
new file mode 100644
index 0000000..fbf1074
--- /dev/null
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_label" msgid="8016145283189546017">"Amadivayisi wokufaka"</string>
+    <string name="keyboard_layouts_label" msgid="6688773268302087545">"Ikhibhodi ye-Android"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"I-English (UK)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"I-English (US)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Isi-Russian"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Isi-Russian, isitayela se-Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Isi-Spanish"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Isi-Swiss French"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Isi-Swiss German"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Isi-Belgian"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Isi-Bulgarian"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Isi-Italian"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Isi-Danish"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Isi-Norwegian"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Isi-Swedish"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Isi-Finnish"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Isi-Croatian"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Isi-Czech"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Isi-Estonian"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Isi-Hungarian"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Isi-Icelandic"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Isi-Brazilian"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Isi-Portuguese"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Isi-Slovak"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Isi-Slovenian"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Isi-Turkish"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Isi-Ukrainian"</string>
+</resources>
diff --git a/packages/Keyguard/res/layout-land/keyguard_host_view.xml b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
index eeb9ee7..9f1c1f0 100644
--- a/packages/Keyguard/res/layout-land/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
@@ -60,6 +60,8 @@
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             androidprv:layout_childType="challenge"
             androidprv:layout_centerWithinArea="0.55">
             <com.android.keyguard.KeyguardSecurityViewFlipper
diff --git a/packages/Keyguard/res/layout-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
index 8498dcf..136b296 100644
--- a/packages/Keyguard/res/layout-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
@@ -31,7 +31,8 @@
     <com.android.keyguard.SlidingChallengeLayout
         android:id="@+id/sliding_layout"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:clipChildren="false">
 
         <FrameLayout
             android:layout_width="match_parent"
@@ -64,6 +65,8 @@
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             androidprv:layout_maxHeight="@dimen/keyguard_security_height"
             androidprv:layout_childType="challenge"
             android:padding="0dp"
diff --git a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
index 77bc9b5..85f6b6d 100644
--- a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -61,6 +61,8 @@
             android:id="@+id/keyguard_security_container"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             androidprv:layout_centerWithinArea="0.5"
             androidprv:layout_childType="challenge"
             android:layout_gravity="center_horizontal|bottom">
diff --git a/packages/Keyguard/res/layout/keyguard_transport_control_view.xml b/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
index a0b59a7..da82e40 100644
--- a/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_transport_control_view.xml
@@ -94,67 +94,12 @@
                     android:textAppearance="?android:attr/textAppearanceSmall"
                     android:textSize="12dp" />
             </RelativeLayout>
-            <LinearLayout
-                android:id="@+id/transient_rating"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:visibility="invisible">
-                <RatingBar
-                    android:id="@+id/transient_rating_bar_stars"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-                <LinearLayout
-                    android:id="@+id/transient_rating_thumbs"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:orientation="horizontal">
-                    <FrameLayout
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_weight="1">
-                        <ImageButton
-                            android:id="@+id/btn_thumbs_up"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center"
-                            android:src="@drawable/ic_media_previous"
-                            android:background="?android:attr/selectableItemBackground"
-                            android:minWidth="48dp"
-                            android:minHeight="48dp"
-                            android:contentDescription="@string/keyguard_accessibility_transport_thumbs_up_description"/>
-                    </FrameLayout>
-                    <FrameLayout
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_weight="1">
-                        <ImageButton
-                            android:id="@+id/btn_thumbs_down"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center"
-                            android:src="@drawable/ic_media_next"
-                            android:background="?android:attr/selectableItemBackground"
-                            android:minWidth="48dp"
-                            android:minHeight="48dp"
-                            android:contentDescription="@string/keyguard_accessibility_transport_thumbs_down_description"/>
-                    </FrameLayout>
-                </LinearLayout>
-                <ToggleButton
-                    android:id="@+id/transient_rating_heart"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:visibility="invisible"
-                    android:minWidth="48dp"
-                    android:minHeight="48dp"
-                    android:contentDescription="@string/keyguard_accessibility_transport_heart_description" />
-            </LinearLayout>
         </FrameLayout>
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layout_marginTop="5dip">
+            android:layout_marginTop="4dp"
+            android:orientation="horizontal">
             <FrameLayout
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
diff --git a/packages/Keyguard/res/values-af/strings.xml b/packages/Keyguard/res/values-af/strings.xml
index 3644660..834f2f6 100644
--- a/packages/Keyguard/res/values-af/strings.xml
+++ b/packages/Keyguard/res/values-af/strings.xml
@@ -72,7 +72,7 @@
     <string name="keyguard_accessibility_transport_play_description" msgid="8146417789511154044">"Speel-knoppie"</string>
     <string name="keyguard_accessibility_transport_stop_description" msgid="7656358482980912216">"Stop-knoppie"</string>
     <string name="keyguard_accessibility_transport_thumbs_up_description" msgid="4535938129663903194">"Laaik baie"</string>
-    <string name="keyguard_accessibility_transport_thumbs_down_description" msgid="8101433677192177861">"Duim omlaag"</string>
+    <string name="keyguard_accessibility_transport_thumbs_down_description" msgid="8101433677192177861">"Laaik niks"</string>
     <string name="keyguard_accessibility_transport_heart_description" msgid="2336943232474689887">"Hart"</string>
     <string name="keyguard_accessibility_show_bouncer" msgid="5425837272418176176">"Ontsluit om voort te gaan"</string>
     <string name="keyguard_accessibility_hide_bouncer" msgid="7896992171878309358">"Begin gekanselleer"</string>
diff --git a/packages/Keyguard/res/values-km-rKH/strings.xml b/packages/Keyguard/res/values-km-rKH/strings.xml
index 75cebac..08af5dd 100644
--- a/packages/Keyguard/res/values-km-rKH/strings.xml
+++ b/packages/Keyguard/res/values-km-rKH/strings.xml
@@ -72,7 +72,7 @@
     <string name="keyguard_accessibility_transport_play_description" msgid="8146417789511154044">"ប៊ូតុង​ចាក់"</string>
     <string name="keyguard_accessibility_transport_stop_description" msgid="7656358482980912216">"ប៊ូតុង​បញ្ឈប់"</string>
     <string name="keyguard_accessibility_transport_thumbs_up_description" msgid="4535938129663903194">"មេដៃ​ឡើង"</string>
-    <string name="keyguard_accessibility_transport_thumbs_down_description" msgid="8101433677192177861">"មេដៃចុះ"</string>
+    <string name="keyguard_accessibility_transport_thumbs_down_description" msgid="8101433677192177861">"មេដៃ​ចុះ"</string>
     <string name="keyguard_accessibility_transport_heart_description" msgid="2336943232474689887">"បេះដូង"</string>
     <string name="keyguard_accessibility_show_bouncer" msgid="5425837272418176176">"ដោះ​សោ ​ដើម្បី​បន្ត"</string>
     <string name="keyguard_accessibility_hide_bouncer" msgid="7896992171878309358">"បាន​បោះបង់​ការ​ចាប់ផ្ដើម"</string>
diff --git a/packages/Keyguard/res/values-mcc262-mnc07/bools.xml b/packages/Keyguard/res/values-mcc262-mnc07/bools.xml
new file mode 100644
index 0000000..6cd4c55
--- /dev/null
+++ b/packages/Keyguard/res/values-mcc262-mnc07/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <!-- Carriers in this locale are sensitive to capitalization of carrier text. 
+         This makes the entire interface consistent by switching back to normal case. -->
+    <bool name="kg_use_all_caps">false</bool>
+</resources>
diff --git a/packages/Keyguard/res/values-mcc262-mnc08/bools.xml b/packages/Keyguard/res/values-mcc262-mnc08/bools.xml
new file mode 100644
index 0000000..6cd4c55
--- /dev/null
+++ b/packages/Keyguard/res/values-mcc262-mnc08/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <!-- Carriers in this locale are sensitive to capitalization of carrier text. 
+         This makes the entire interface consistent by switching back to normal case. -->
+    <bool name="kg_use_all_caps">false</bool>
+</resources>
diff --git a/packages/Keyguard/res/values-mcc262-mnc11/bools.xml b/packages/Keyguard/res/values-mcc262-mnc11/bools.xml
new file mode 100644
index 0000000..6cd4c55
--- /dev/null
+++ b/packages/Keyguard/res/values-mcc262-mnc11/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <!-- Carriers in this locale are sensitive to capitalization of carrier text. 
+         This makes the entire interface consistent by switching back to normal case. -->
+    <bool name="kg_use_all_caps">false</bool>
+</resources>
diff --git a/packages/Keyguard/res/values-sw720dp/dimens.xml b/packages/Keyguard/res/values-sw720dp/dimens.xml
index 0790b79..4853a7b 100644
--- a/packages/Keyguard/res/values-sw720dp/dimens.xml
+++ b/packages/Keyguard/res/values-sw720dp/dimens.xml
@@ -60,4 +60,8 @@
 
     <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
+
+    <!-- Default clock parameters -->
+    <dimen name="widget_label_font_size">19dp</dimen>
+
 </resources>
diff --git a/packages/Keyguard/res/values-uk/strings.xml b/packages/Keyguard/res/values-uk/strings.xml
index 3c004ce..01bf687 100644
--- a/packages/Keyguard/res/values-uk/strings.xml
+++ b/packages/Keyguard/res/values-uk/strings.xml
@@ -73,7 +73,7 @@
     <string name="keyguard_accessibility_transport_stop_description" msgid="7656358482980912216">"Кнопка \"Зупинити\""</string>
     <string name="keyguard_accessibility_transport_thumbs_up_description" msgid="4535938129663903194">"Подобається"</string>
     <string name="keyguard_accessibility_transport_thumbs_down_description" msgid="8101433677192177861">"Не подобається"</string>
-    <string name="keyguard_accessibility_transport_heart_description" msgid="2336943232474689887">"Серце"</string>
+    <string name="keyguard_accessibility_transport_heart_description" msgid="2336943232474689887">"Рейтинг"</string>
     <string name="keyguard_accessibility_show_bouncer" msgid="5425837272418176176">"Розблокуйте, щоб продовжити"</string>
     <string name="keyguard_accessibility_hide_bouncer" msgid="7896992171878309358">"Запуск скасовано"</string>
     <string name="keyguard_accessibility_delete_widget_start" msgid="4096550552634391451">"Відпустіть <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>, щоб видалити."</string>
diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml
index 8039b09..a1ad120 100644
--- a/packages/Keyguard/res/values/dimens.xml
+++ b/packages/Keyguard/res/values/dimens.xml
@@ -149,11 +149,11 @@
     used on tablets; on phones, this size is determined by the space left by the
     security mode. -->
     <dimen name="kg_small_widget_height">160dp</dimen>
-    
+
     <!-- Default clock parameters -->
     <dimen name="bottom_text_spacing_digital">-8dp</dimen>
     <dimen name="label_font_size">14dp</dimen>
-    <dimen name="widget_label_font_size">12dp</dimen>
+    <dimen name="widget_label_font_size">14dp</dimen>
     <dimen name="widget_big_font_size">80dp</dimen>
     <dimen name="big_font_size">120dp</dimen>
 
diff --git a/packages/Keyguard/res/values/donottranslate.xml b/packages/Keyguard/res/values/donottranslate.xml
index 5ee226b..16f5a3e 100644
--- a/packages/Keyguard/res/values/donottranslate.xml
+++ b/packages/Keyguard/res/values/donottranslate.xml
@@ -15,6 +15,12 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- String matching the lock screen format for displaying the date. -->
+    <!-- Skeleton string format for displaying the date. -->
     <string name="abbrev_wday_month_day_no_year">EEEMMMMd</string>
+
+    <!-- Skeleton string format for displaying the time in 12-hour format. -->
+    <string name="clock_12hr_format">hm</string>
+
+    <!-- Skeleton string format for displaying the time in 24-hour format. -->
+    <string name="clock_24hr_format">Hm</string>
 </resources>
diff --git a/packages/Keyguard/res/values/styles.xml b/packages/Keyguard/res/values/styles.xml
index 9fd8f31..d4f98af 100644
--- a/packages/Keyguard/res/values/styles.xml
+++ b/packages/Keyguard/res/values/styles.xml
@@ -52,13 +52,13 @@
         <item name="android:windowEnterAnimation">@anim/lock_screen_enter</item>
         <item name="android:windowExitAnimation">@anim/lock_screen_exit</item>
     </style>
-    
+
     <!-- Built-in clock widget stuff -->
     <style name="widget_label">
         <item name="android:textStyle">bold</item>
         <item name="android:textAllCaps">true</item>
         <item name="android:fontFamily">sans-serif-condensed</item>
-        <item name="android:textSize">@dimen/kg_status_line_font_size</item>
+        <item name="android:textSize">@dimen/widget_label_font_size</item>
     </style>
     <style name="big_thin">
         <item name="android:textSize">@dimen/big_font_size</item>
diff --git a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
index 146c092..7d1f24f 100644
--- a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
+++ b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java
@@ -19,11 +19,13 @@
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Color;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -55,15 +57,17 @@
     private final WindowManager mWindowManager;
     private final Point mRenderedSize = new Point();
     private final int[] mTmpLoc = new int[2];
-    private final Rect mTmpRect = new Rect();
 
     private long mLaunchCameraStart;
     private boolean mActive;
     private boolean mTransitioning;
     private boolean mDown;
 
+    private final Rect mInsets = new Rect();
+
     private FixedSizeFrameLayout mPreview;
     private View mFullscreenPreview;
+    private View mFakeNavBar;
 
     private final Runnable mTransitionToCameraRunnable = new Runnable() {
         @Override
@@ -211,10 +215,11 @@
 
     private void render() {
         final View root = getRootView();
-        final int width = root.getWidth();
-        final int height = root.getHeight();
+        final int width = root.getWidth() - mInsets.right;    // leave room
+        final int height = root.getHeight() - mInsets.bottom; // for bars
         if (mRenderedSize.x == width && mRenderedSize.y == height) {
-            if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s", width, height));
+            if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s %d%%",
+                    width, height, (int)(100*mPreview.getScaleX())));
             return;
         }
         if (width == 0 || height == 0) {
@@ -246,8 +251,8 @@
         mPreview.setTranslationY(pvTransY);
 
         mRenderedSize.set(width, height);
-        if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s instance=%s",
-                width, height, instanceId()));
+        if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s %d%% instance=%s",
+                width, height, (int)(100*mPreview.getScaleX()), instanceId()));
     }
 
     private void transitionToCamera() {
@@ -257,24 +262,34 @@
 
         enableWindowExitAnimation(false);
 
+        final int navHeight = mInsets.bottom;
+        final int navWidth = mInsets.right;
+
         mPreview.getLocationInWindow(mTmpLoc);
         final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
         final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
 
         final ViewGroup root = (ViewGroup) getRootView();
+
+        if (DEBUG) {
+            Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " "
+                    + root.getWidth() + "x" + root.getHeight());
+        }
+
         if (mFullscreenPreview == null) {
             mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
             mFullscreenPreview.setClickable(false);
-            root.addView(mFullscreenPreview);
+            root.addView(mFullscreenPreview, new FrameLayout.LayoutParams(
+                        root.getWidth() - navWidth,
+                        root.getHeight() - navHeight));
         }
 
-        root.getWindowVisibleDisplayFrame(mTmpRect);
-        final float fsHeight = mTmpRect.height();
-        final float fsCenter = mTmpRect.top + fsHeight / 2;
+        final float fsHeight = root.getHeight() - navHeight;
+        final float fsCenter = root.getTop() + fsHeight / 2;
 
-        final float fsScaleY = pvHeight / fsHeight;
+        final float fsScaleY = mPreview.getScaleY();
         final float fsTransY = pvCenter - fsCenter;
-        final float fsScaleX = mPreview.getScaleX();
+        final float fsScaleX = fsScaleY;
 
         mPreview.setVisibility(View.GONE);
         mFullscreenPreview.setVisibility(View.VISIBLE);
@@ -290,6 +305,36 @@
             .setDuration(WIDGET_ANIMATION_DURATION)
             .withEndAction(mPostTransitionToCameraEndAction)
             .start();
+
+        if (navHeight > 0 || navWidth > 0) {
+            final boolean atBottom = navHeight > 0;
+            if (mFakeNavBar == null) {
+                mFakeNavBar = new View(mContext);
+                mFakeNavBar.setBackgroundColor(Color.BLACK);
+                root.addView(mFakeNavBar, new FrameLayout.LayoutParams(
+                            atBottom ? FrameLayout.LayoutParams.MATCH_PARENT
+                                     : navWidth,
+                            atBottom ? navHeight
+                                     : FrameLayout.LayoutParams.MATCH_PARENT,
+                            atBottom ? Gravity.BOTTOM|Gravity.FILL_HORIZONTAL
+                                     : Gravity.RIGHT|Gravity.FILL_VERTICAL));
+                mFakeNavBar.setPivotY(navHeight);
+                mFakeNavBar.setPivotX(navWidth);
+            }
+            mFakeNavBar.setAlpha(0f);
+            if (atBottom) {
+                mFakeNavBar.setScaleY(0.5f);
+            } else {
+                mFakeNavBar.setScaleX(0.5f);
+            }
+            mFakeNavBar.setVisibility(View.VISIBLE);
+            mFakeNavBar.animate()
+                .alpha(1f)
+                .scaleY(1f)
+                .scaleY(1f)
+                .setDuration(WIDGET_ANIMATION_DURATION)
+                .start();
+        }
         mCallbacks.onLaunchingCamera();
     }
 
@@ -397,6 +442,10 @@
             mFullscreenPreview.animate().cancel();
             mFullscreenPreview.setVisibility(View.GONE);
         }
+        if (mFakeNavBar != null) {
+            mFakeNavBar.animate().cancel();
+            mFakeNavBar.setVisibility(View.GONE);
+        }
         enableWindowExitAnimation(true);
     }
 
@@ -404,6 +453,10 @@
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
                 w, h, oldw, oldh, SystemClock.uptimeMillis()));
+        if ((w != oldw && oldw > 0) || (h != oldh && oldh > 0)) {
+            // we can't trust the old geometry anymore; force a re-render
+            mRenderedSize.x = mRenderedSize.y = -1;
+        }
         mHandler.post(mRenderRunnable);
         super.onSizeChanged(w, h, oldw, oldh);
     }
@@ -454,4 +507,9 @@
     private String instanceId() {
         return Integer.toHexString(hashCode());
     }
+
+    public void setInsets(Rect insets) {
+        if (DEBUG) Log.d(TAG, "setInsets: " + insets);
+        mInsets.set(insets);
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index fb2eeda..461fd77 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -82,13 +82,18 @@
 
         // if the user is currently locked out, enforce it.
         long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
-        if (deadline != 0) {
+        if (shouldLockout(deadline)) {
             handleAttemptLockout(deadline);
         } else {
             resetState();
         }
     }
 
+    // Allow subclasses to override this behavior
+    protected boolean shouldLockout(long deadline) {
+        return deadline != 0;
+    }
+
     protected abstract int getPasswordTextViewId();
     protected abstract void resetState();
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index c4be72f..fdc06a6 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -90,9 +90,6 @@
     private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
     private int mAppWidgetToShow;
 
-    private boolean mCheckAppWidgetConsistencyOnBootCompleted = false;
-    private boolean mCleanupAppWidgetsOnBootCompleted = false;
-
     protected OnDismissAction mDismissAction;
 
     protected int mFailedAttempts;
@@ -117,8 +114,6 @@
 
     private KeyguardMultiUserSelectorView mKeyguardMultiUserSelectorView;
 
-    private boolean mIsScreenOn;
-
     protected int mClientGeneration;
 
     protected boolean mShowSecurityWhenReturn;
@@ -127,6 +122,8 @@
 
     private MyOnClickHandler mOnClickHandler = new MyOnClickHandler(this);
 
+    private Runnable mPostBootCompletedRunnable;
+
     /*package*/ interface UserSwitcherCallback {
         void hideSecurityView(int duration);
         void showSecurityView();
@@ -185,8 +182,6 @@
         mAppWidgetHost = new AppWidgetHost(userContext, APPWIDGET_HOST_ID, mOnClickHandler,
                 Looper.myLooper());
 
-        cleanupAppWidgetIds();
-
         mAppWidgetManager = AppWidgetManager.getInstance(userContext);
 
         mSecurityModel = new KeyguardSecurityModel(context);
@@ -228,27 +223,21 @@
     }
 
     private void cleanupAppWidgetIds() {
-        // Since this method may delete a widget (which we can't do until boot completed) we
-        // may have to defer it until after boot complete.
-        if (!KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) {
-            mCleanupAppWidgetsOnBootCompleted = true;
-            return;
-        }
-        if (!mSafeModeEnabled && !widgetsDisabled()) {
-            // Clean up appWidgetIds that are bound to lockscreen, but not actually used
-            // This is only to clean up after another bug: we used to not call
-            // deleteAppWidgetId when a user manually deleted a widget in keyguard. This code
-            // shouldn't have to run more than once per user. AppWidgetProviders rely on callbacks
-            // that are triggered by deleteAppWidgetId, which is why we're doing this
-            int[] appWidgetIdsInKeyguardSettings = mLockPatternUtils.getAppWidgets();
-            int[] appWidgetIdsBoundToHost = mAppWidgetHost.getAppWidgetIds();
-            for (int i = 0; i < appWidgetIdsBoundToHost.length; i++) {
-                int appWidgetId = appWidgetIdsBoundToHost[i];
-                if (!contains(appWidgetIdsInKeyguardSettings, appWidgetId)) {
-                    Log.d(TAG, "Found a appWidgetId that's not being used by keyguard, deleting id "
-                            + appWidgetId);
-                    mAppWidgetHost.deleteAppWidgetId(appWidgetId);
-                }
+        if (mSafeModeEnabled || widgetsDisabled()) return;
+
+        // Clean up appWidgetIds that are bound to lockscreen, but not actually used
+        // This is only to clean up after another bug: we used to not call
+        // deleteAppWidgetId when a user manually deleted a widget in keyguard. This code
+        // shouldn't have to run more than once per user. AppWidgetProviders rely on callbacks
+        // that are triggered by deleteAppWidgetId, which is why we're doing this
+        int[] appWidgetIdsInKeyguardSettings = mLockPatternUtils.getAppWidgets();
+        int[] appWidgetIdsBoundToHost = mAppWidgetHost.getAppWidgetIds();
+        for (int i = 0; i < appWidgetIdsBoundToHost.length; i++) {
+            int appWidgetId = appWidgetIdsBoundToHost[i];
+            if (!contains(appWidgetIdsInKeyguardSettings, appWidgetId)) {
+                Log.d(TAG, "Found a appWidgetId that's not being used by keyguard, deleting id "
+                        + appWidgetId);
+                mAppWidgetHost.deleteAppWidgetId(appWidgetId);
             }
         }
     }
@@ -266,14 +255,9 @@
             new KeyguardUpdateMonitorCallback() {
         @Override
         public void onBootCompleted() {
-            if (mCheckAppWidgetConsistencyOnBootCompleted) {
-                checkAppWidgetConsistency();
-                mSwitchPageRunnable.run();
-                mCheckAppWidgetConsistencyOnBootCompleted = false;
-            }
-            if (mCleanupAppWidgetsOnBootCompleted) {
-                cleanupAppWidgetIds();
-                mCleanupAppWidgetsOnBootCompleted = false;
+            if (mPostBootCompletedRunnable != null) {
+                mPostBootCompletedRunnable.run();
+                mPostBootCompletedRunnable = null;
             }
         }
         @Override
@@ -398,12 +382,29 @@
 
         setBackButtonEnabled(false);
 
-        addDefaultWidgets();
-
-        addWidgetsFromSettings();
-        if (!shouldEnableAddWidget()) {
-            mAppWidgetContainer.setAddWidgetEnabled(false);
+        if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) {
+            updateAndAddWidgets();
+        } else {
+            // We can't add widgets until after boot completes because AppWidgetHost may try
+            // to contact the providers.  Do it later.
+            mPostBootCompletedRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    updateAndAddWidgets();
+                }
+            };
         }
+
+        showPrimarySecurityScreen(false);
+        updateSecurityViews();
+        enableUserSelectorIfNecessary();
+    }
+
+    private void updateAndAddWidgets() {
+        cleanupAppWidgetIds();
+        addDefaultWidgets();
+        addWidgetsFromSettings();
+        maybeEnableAddButton();
         checkAppWidgetConsistency();
 
         // Don't let the user drag the challenge down if widgets are disabled.
@@ -411,12 +412,17 @@
             mSlidingChallengeLayout.setEnableChallengeDragging(!widgetsDisabled());
         }
 
+        // Select the appropriate page
         mSwitchPageRunnable.run();
+
         // This needs to be called after the pages are all added.
         mViewStateManager.showUsabilityHints();
+    }
 
-        showPrimarySecurityScreen(false);
-        updateSecurityViews();
+    private void maybeEnableAddButton() {
+        if (!shouldEnableAddWidget()) {
+            mAppWidgetContainer.setAddWidgetEnabled(false);
+        }
     }
 
     private void setBackButtonEnabled(boolean enabled) {
@@ -646,7 +652,6 @@
     }
 
     private void showAlmostAtWipeDialog(int attempts, int remaining) {
-        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
         String message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
                 attempts, remaining);
         showDialog(null, message);
@@ -1025,7 +1030,6 @@
     @Override
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
-        mIsScreenOn = true;
         showPrimarySecurityScreen(false);
         getSecurityView(mCurrentSecuritySelection).onResume(KeyguardSecurityView.SCREEN_ON);
 
@@ -1045,14 +1049,15 @@
     public void onScreenTurnedOff() {
         if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
                 Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
-        mIsScreenOn = false;
         // Once the screen turns off, we no longer consider this to be first boot and we want the
         // biometric unlock to start next time keyguard is shown.
         KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
         // We use mAppWidgetToShow to show a particular widget after you add it-- once the screen
         // turns off we reset that behavior
         clearAppWidgetToShow();
-        checkAppWidgetConsistency();
+        if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) {
+            checkAppWidgetConsistency();
+        }
         showPrimarySecurityScreen(true);
         getSecurityView(mCurrentSecuritySelection).onPause();
         CameraWidgetFrame cameraPage = findCameraPage();
@@ -1224,8 +1229,6 @@
                 mAppWidgetContainer.addWidget(cameraWidget);
             }
         }
-
-        enableUserSelectorIfNecessary();
     }
 
     /**
@@ -1311,12 +1314,6 @@
     }
 
     public void checkAppWidgetConsistency() {
-        // Since this method may bind a widget (which we can't do until boot completed) we
-        // may have to defer it until after boot complete.
-        if (!KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()) {
-            mCheckAppWidgetConsistencyOnBootCompleted = true;
-            return;
-        }
         final int childCount = mAppWidgetContainer.getChildCount();
         boolean widgetPageExists = false;
         for (int i = 0; i < childCount; i++) {
@@ -1454,6 +1451,9 @@
         mInsets.set(insets);
         if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setInsets(mInsets);
         if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setInsets(mInsets);
+
+        final CameraWidgetFrame cameraWidget = findCameraPage();
+        if (cameraWidget != null) cameraWidget.setInsets(mInsets);
     }
 
     @Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
index d7c5fe2..36b2446 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java
@@ -141,6 +141,10 @@
             checkPermission();
             mKeyguardViewMediator.launchCamera();
         }
+        public void onBootCompleted() {
+            checkPermission();
+            mKeyguardViewMediator.onBootCompleted();
+        }
     };
 
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
index 865a7c4..5059407 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
@@ -56,6 +56,12 @@
     }
 
     @Override
+    protected boolean shouldLockout(long deadline) {
+        // SIM PIN doesn't have a timed lockout
+        return false;
+    }
+
+    @Override
     protected int getPasswordTextViewId() {
         return R.id.pinEntry;
     }
@@ -169,7 +175,7 @@
     @Override
     protected void verifyPasswordAndUnlock() {
         String entry = mPasswordEntry.getText().toString();
-        
+
         if (entry.length() < 4) {
             // otherwise, display a message to the user, and don't submit.
             mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint, true);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index 7424fab..2ae4cc7 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -107,6 +107,12 @@
     }
 
     @Override
+    protected boolean shouldLockout(long deadline) {
+        // SIM PUK doesn't have a timed lockout
+        return false;
+    }
+
+    @Override
     protected int getPasswordTextViewId() {
         return R.id.pinEntry;
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
index 57fd82c..d933275 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.AttributeSet;
@@ -39,6 +40,7 @@
 
     private TextView mAlarmStatusView;
     private TextClock mDateView;
+    private TextClock mClockView;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -88,6 +90,7 @@
         super.onFinishInflate();
         mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
         mDateView = (TextClock) findViewById(R.id.date_view);
+        mClockView = (TextClock) findViewById(R.id.clock_view);
         mLockPatternUtils = new LockPatternUtils(getContext());
         final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
         setEnableMarquee(screenOn);
@@ -95,10 +98,27 @@
     }
 
     protected void refresh() {
-        final String fmt = DateFormat.getBestDateTimePattern(Locale.getDefault(),
-                mContext.getResources().getString(R.string.abbrev_wday_month_day_no_year));
-        mDateView.setFormat24Hour(fmt);
-        mDateView.setFormat12Hour(fmt);
+        Resources res = mContext.getResources();
+        Locale locale = Locale.getDefault();
+        final String dateFormat = DateFormat.getBestDateTimePattern(locale,
+                res.getString(R.string.abbrev_wday_month_day_no_year));
+
+        mDateView.setFormat24Hour(dateFormat);
+        mDateView.setFormat12Hour(dateFormat);
+
+        // 12-hour clock.
+        // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+        // format.  The following code removes the AM/PM indicator if we didn't want it.
+        final String clock12skel = res.getString(R.string.clock_12hr_format);
+        String clock12hr = DateFormat.getBestDateTimePattern(locale, clock12skel);
+        clock12hr = clock12skel.contains("a") ? clock12hr : clock12hr.replaceAll("a", "").trim();
+        mClockView.setFormat12Hour(clock12hr);
+
+        // 24-hour clock
+        final String clock24skel = res.getString(R.string.clock_24hr_format);
+        final String clock24hr = DateFormat.getBestDateTimePattern(locale, clock24skel);
+        mClockView.setFormat24Hour(clock24hr);
+
         refreshAlarmStatus();
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
index ca4892d..a7af6a4 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
@@ -101,9 +101,7 @@
             new RemoteController.OnClientUpdateListener() {
         @Override
         public void onClientChange(boolean clearing) {
-            if (clearing) {
-                clearMetadata();
-            }
+            clearMetadata();
         }
 
         @Override
@@ -302,6 +300,7 @@
             mPopulateMetadataWhenAttached = null;
         }
         if (DEBUG) Log.v(TAG, "Registering TCV " + this);
+        mMetadata.clear();
         mAudioManager.registerRemoteController(mRemoteController);
         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitor);
     }
@@ -321,7 +320,9 @@
         if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
         mAudioManager.unregisterRemoteController(mRemoteController);
         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
+        mMetadata.clear();
         mUserSeeking = false;
+        removeCallbacks(mUpdateSeekBars);
     }
 
     void setBadgeIcon(Drawable bmp) {
@@ -394,10 +395,10 @@
             Log.e(TAG, "Couldn't get remote control client package icon", e);
         }
         setBadgeIcon(badgeIcon);
-        if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
-            mTrackTitle.setText(mMetadata.trackTitle);
-        }
-        StringBuilder sb = new StringBuilder();
+        mTrackTitle.setText(!TextUtils.isEmpty(mMetadata.trackTitle)
+                ? mMetadata.trackTitle : null);
+
+        final StringBuilder sb = new StringBuilder();
         if (!TextUtils.isEmpty(mMetadata.artist)) {
             if (sb.length() != 0) {
                 sb.append(" - ");
@@ -410,7 +411,10 @@
             }
             sb.append(mMetadata.albumTitle);
         }
-        mTrackArtistAlbum.setText(sb.toString());
+
+        final String trackArtistAlbum = sb.toString();
+        mTrackArtistAlbum.setText(!TextUtils.isEmpty(trackArtistAlbum) ?
+                trackArtistAlbum : null);
 
         if (mMetadata.duration >= 0) {
             setSeekBarsEnabled(true);
@@ -511,7 +515,7 @@
         if ((flags & flag) != 0) {
             view.setVisibility(View.VISIBLE);
         } else {
-            view.setVisibility(View.GONE);
+            view.setVisibility(View.INVISIBLE);
         }
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 45cd3d4..520cea3 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -635,15 +635,14 @@
      * PhoneWindowManager in this case.
      */
     protected void dispatchBootCompleted() {
-        if (!mBootCompleted) {
-            mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
-        }
+        mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
     }
 
     /**
      * Handle {@link #MSG_BOOT_COMPLETED}
      */
     protected void handleBootCompleted() {
+        if (mBootCompleted) return;
         mBootCompleted = true;
         mAudioManager = new AudioManager(mContext);
         mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
index 58ca0b0..fd7cae6 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
@@ -85,6 +85,7 @@
         public void onSetBackground(Bitmap bmp) {
             mKeyguardHost.setCustomBackground(bmp != null ?
                     new BitmapDrawable(mContext.getResources(), bmp) : null);
+            updateShowWallpaper(bmp == null);
         }
     };
 
@@ -379,6 +380,16 @@
         mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
     }
 
+    void updateShowWallpaper(boolean show) {
+        if (show) {
+            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        } else {
+            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        }
+
+        mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+    }
+
     public void setNeedsInput(boolean needsInput) {
         mNeedsInput = needsInput;
         if (mWindowLayoutParams != null) {
@@ -420,7 +431,14 @@
     public synchronized void onScreenTurnedOn(final IKeyguardShowCallback callback) {
         if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
         mScreenOn = true;
-        final IBinder token = mKeyguardHost == null ? null : mKeyguardHost.getWindowToken();
+
+        // If keyguard is not showing, we need to inform PhoneWindowManager with a null
+        // token so it doesn't wait for us to draw...
+        final IBinder token = isShowing() ? mKeyguardHost.getWindowToken() : null;
+
+        if (DEBUG && token == null) Slog.v(TAG, "send wm null token: "
+                + (mKeyguardHost == null ? "host was null" : "not showing"));
+
         if (mKeyguardView != null) {
             mKeyguardView.onScreenTurnedOn();
 
@@ -489,6 +507,7 @@
                             lastView.cleanUp();
                             // Let go of any large bitmaps.
                             mKeyguardHost.setCustomBackground(null);
+                            updateShowWallpaper(true);
                             mKeyguardHost.removeView(lastView);
                         }
                     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
index a37a3a4..b92ae90 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java
@@ -530,9 +530,6 @@
             mSystemReady = true;
             mUpdateMonitor.registerCallback(mUpdateCallback);
 
-            // Send boot completed message if it hasn't already been sent.
-            mUpdateMonitor.dispatchBootCompleted();
-
             // Suppress biometric unlock right after boot until things have settled if it is the
             // selected security method, otherwise unsuppress it.  It must be unsuppressed if it is
             // not the selected security method for the following reason:  if the user starts
@@ -1366,4 +1363,8 @@
         Message msg = mHandler.obtainMessage(LAUNCH_CAMERA);
         mHandler.sendMessage(msg);
     }
+
+    public void onBootCompleted() {
+        mUpdateMonitor.dispatchBootCompleted();
+    }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
index d1862cd..8e39628 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java
@@ -17,12 +17,14 @@
 
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
 import android.view.View;
 
 public class KeyguardViewStateManager implements
         SlidingChallengeLayout.OnChallengeScrolledListener,
         ChallengeLayout.OnBouncerStateChangedListener {
 
+    private static final String TAG = "KeyguardViewStateManager";
     private KeyguardWidgetPager mKeyguardWidgetPager;
     private ChallengeLayout mChallengeLayout;
     private KeyguardHostView mKeyguardHostView;
@@ -100,18 +102,20 @@
     }
 
     public void fadeOutSecurity(int duration) {
-        ((View) mKeyguardSecurityContainer).animate().alpha(0).setDuration(duration);
+        ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration).start();
     }
 
     public void fadeInSecurity(int duration) {
-        ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration);
+        ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration).start();
     }
 
     public void onPageBeginMoving() {
         if (mChallengeLayout.isChallengeOverlapping() &&
                 mChallengeLayout instanceof SlidingChallengeLayout) {
             SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
-            scl.fadeOutChallenge();
+            if (!mKeyguardWidgetPager.isWarping()) {
+                scl.fadeOutChallenge();
+            }
             mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage();
         }
         // We use mAppWidgetToShow to show a particular widget after you add it--
@@ -133,7 +137,15 @@
     public void onPageSwitching(View newPage, int newPageIndex) {
         if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) {
             boolean isCameraPage = newPage instanceof CameraWidgetFrame;
-            ((SlidingChallengeLayout) mChallengeLayout).setChallengeInteractive(!isCameraPage);
+            SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
+            scl.setChallengeInteractive(!isCameraPage);
+            if (isCameraPage) {
+                scl.fadeOutChallenge();
+            }
+            final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility();
+            final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH)
+                    : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH);
+            mKeyguardWidgetPager.setSystemUiVisibility(newFlags);
         }
 
         // If the page we're settling to is the same as we started on, and the action of
@@ -174,13 +186,15 @@
     }
 
     public void onPageBeginWarp() {
-        // fadeOutSecurity(WARP_FADE_DURATION);
-        // mKeyguardWidgetPager.showNonWarpViews(WARP_FADE_DURATION, false);
+        fadeOutSecurity(SlidingChallengeLayout.CHALLENGE_FADE_OUT_DURATION);
+        View frame = mKeyguardWidgetPager.getPageAt(mKeyguardWidgetPager.getPageWarpIndex());
+        ((KeyguardWidgetFrame)frame).showFrame(this);
     }
 
     public void onPageEndWarp() {
-        // fadeInSecurity(WARP_FADE_DURATION);
-        // mKeyguardWidgetPager.showNonWarpViews(WARP_FADE_DURATION, true);
+        fadeInSecurity(SlidingChallengeLayout.CHALLENGE_FADE_IN_DURATION);
+        View frame = mKeyguardWidgetPager.getPageAt(mKeyguardWidgetPager.getPageWarpIndex());
+        ((KeyguardWidgetFrame)frame).hideFrame(this);
     }
 
     private int getChallengeTopRelativeToFrame(KeyguardWidgetFrame frame, int top) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
index f8857ab..704af6e 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java
@@ -188,11 +188,13 @@
 
     @Override
     public void onPageBeginWarp() {
+        showOutlinesAndSidePages();
         mViewStateManager.onPageBeginWarp();
     }
 
     @Override
     public void onPageEndWarp() {
+        hideOutlinesAndSidePages();
         mViewStateManager.onPageEndWarp();
     }
 
@@ -495,7 +497,7 @@
     }
 
     public float getAlphaForPage(int screenCenter, int index, boolean showSidePages) {
-        if (getPageWarpIndex() != -1) {
+        if (isWarping()) {
             return index == getPageWarpIndex() ? 1.0f : 0.0f;
         }
         if (showSidePages) {
@@ -949,17 +951,17 @@
                     // to keep event dispatch happy.
                     mCameraEventInProgress = true;
                     userActivity();
-                    startWarp(cameraPage);
+                    startPageWarp(cameraPage);
                     break;
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
                     mCameraEventInProgress = false;
-                    endWarp = true;
+                    endWarp = isWarping();
                     break;
             }
             dispatchTouchEvent(event);
             // This has to happen after the event has been handled by the real widget pager
-            if (endWarp) endWarp();
+            if (endWarp) stopPageWarp();
         }
         endCameraEvent();
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
index 528f988..4cfc9dc 100644
--- a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
@@ -195,11 +195,9 @@
             // and that the window is LAYOUT_IN_SCREEN.
             virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop() - mInsets.top;
         }
-        if (lp.childType == LayoutParams.CHILD_TYPE_WIDGET ||
-                lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
-            // Always measure the widget pager/user switcher as if there were no IME insets
-            // on the window. We want to avoid resizing widgets when possible as it can
-            // be ugly/expensive. This lets us simply clip them instead.
+        if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
+            // Always measure the user switcher as if there were no IME insets
+            // on the window.
             return virtualHeight - heightUsed;
         } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) {
             return height;
diff --git a/packages/Keyguard/src/com/android/keyguard/PagedView.java b/packages/Keyguard/src/com/android/keyguard/PagedView.java
index 666227c..9d237dc 100644
--- a/packages/Keyguard/src/com/android/keyguard/PagedView.java
+++ b/packages/Keyguard/src/com/android/keyguard/PagedView.java
@@ -44,9 +44,11 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -60,12 +62,13 @@
  * sequential list of "pages"
  */
 public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+    private static final int WARP_SNAP_DURATION = 160;
     private static final String TAG = "WidgetPagedView";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_WARP = false;
     protected static final int INVALID_PAGE = -1;
-    private static final int WARP_PEEK_ANIMATION_DURATION = 250;
-    private static final float WARP_ANIMATE_AMOUNT = -40.0f; // in dip
+    private static final int WARP_PEEK_ANIMATION_DURATION = 150;
+    private static final float WARP_ANIMATE_AMOUNT = -75.0f; // in dip
 
     // the min drag distance for a fling to register, to prevent random page shifts
     private static final int MIN_LENGTH_FOR_FLING = 25;
@@ -132,6 +135,7 @@
     protected final static int TOUCH_STATE_PREV_PAGE = 2;
     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
     protected final static int TOUCH_STATE_REORDERING = 4;
+    protected final static int TOUCH_STATE_READY = 5; // when finger is down
 
     protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
     protected final static float TOUCH_SLOP_SCALE = 1.0f;
@@ -258,6 +262,8 @@
     // Page warping
     private int mPageSwapIndex = -1; // the page we swapped out if needed
     private int mPageWarpIndex = -1; // the page we intend to warp
+    private boolean mWarpPageExposed;
+    private ViewPropertyAnimator mWarpAnimation;
 
     private boolean mIsCameraEvent;
     private float mWarpPeekAmount;
@@ -484,7 +490,7 @@
         if (DEBUG_WARP) Log.v(TAG, "pageBeginMoving(" + mIsPageMoving + ")");
         if (!mIsPageMoving) {
             mIsPageMoving = true;
-            if (mPageWarpIndex != -1) {
+            if (isWarping()) {
                 onPageBeginWarp();
                 if (mPageSwapIndex != -1) {
                     swapPages(mPageSwapIndex, mPageWarpIndex);
@@ -498,12 +504,12 @@
         if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")");
         if (mIsPageMoving) {
             mIsPageMoving = false;
-            if (mPageWarpIndex != -1) {
+            if (isWarping()) {
                 if (mPageSwapIndex != -1) {
                     swapPages(mPageSwapIndex, mPageWarpIndex);
-                    resetPageWarp();
                 }
                 onPageEndWarp();
+                resetPageWarp();
             }
             onPageEndMoving();
         }
@@ -1125,7 +1131,7 @@
 
             case MotionEvent.ACTION_DOWN: {
                 if (mIsCameraEvent) {
-                    animateWarpPageOnScreen();
+                    animateWarpPageOnScreen("interceptTouch(): DOWN");
                 }
                 // Remember where the motion event started
                 saveDownState(ev);
@@ -1192,6 +1198,7 @@
 
     private void setTouchState(int touchState) {
         if (mTouchState != touchState) {
+            if (DEBUG_WARP) Log.v(TAG, "mTouchState changing to " + touchState);
             onTouchStateChanged(touchState);
             mTouchState = touchState;
         }
@@ -1394,7 +1401,11 @@
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 pageBeginMoving();
             } else {
-                animateWarpPageOnScreen();
+                setTouchState(TOUCH_STATE_READY);
+            }
+
+            if (mIsCameraEvent) {
+                animateWarpPageOnScreen("onTouch(): DOWN");
             }
             break;
 
@@ -1416,7 +1427,10 @@
                 if (Math.abs(deltaX) >= 1.0f) {
                     mTouchX += deltaX;
                     mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
-                    if (!mDeferScrollUpdate) {
+                    if (isWarping()) {
+                        KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
+                        v.setTranslationX(v.getTranslationX() - deltaX);
+                    } else if (!mDeferScrollUpdate) {
                         scrollBy((int) deltaX, 0);
                         if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
                     } else {
@@ -1542,6 +1556,9 @@
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
+
+                if (pointerIndex == -1) return true;
+
                 final float x = ev.getX(pointerIndex);
                 final VelocityTracker velocityTracker = mVelocityTracker;
                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -1571,7 +1588,8 @@
                 // move to the left and fling to the right will register as a fling to the right.
                 if (((isSignificantMove && deltaX > 0 && !isFling) ||
                         (isFling && velocityX > 0)) && mCurrentPage > 0) {
-                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+                    finalPage = returnToOriginalPage || isWarping()
+                            ? mCurrentPage : mCurrentPage - 1;
                     snapToPageWithVelocity(finalPage, velocityX);
                 } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
                         (isFling && velocityX < 0)) &&
@@ -1626,6 +1644,10 @@
                     onDropToDelete();
                 }
             } else {
+                if (DEBUG_WARP) Log.v(TAG, "calling onUnhandledTap()");
+                if (mWarpPageExposed && !isAnimatingWarpPage()) {
+                    animateWarpPageOffScreen("unhandled tap", true);
+                }
                 onUnhandledTap(ev);
             }
 
@@ -1790,7 +1812,15 @@
     }
 
     protected void snapToDestination() {
-        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+        final int newPage = getPageNearestToCenterOfScreen();
+        if (isWarping()) {
+            cancelWarpAnimation("snapToDestination", mCurrentPage != newPage);
+        }
+        snapToPage(newPage, getPageSnapDuration());
+    }
+
+    private int getPageSnapDuration() {
+        return isWarping() ? WARP_SNAP_DURATION : PAGE_SNAP_ANIMATION_DURATION;
     }
 
     private static class ScrollInterpolator implements Interpolator {
@@ -1817,6 +1847,10 @@
         whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
         int halfScreenSize = getViewportWidth() / 2;
 
+        if (isWarping()) {
+            cancelWarpAnimation("snapToPageWithVelocity", mCurrentPage != whichPage);
+        }
+
         if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
         if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
                 + getViewportWidth() + ", " + getChildWidth(whichPage));
@@ -1827,7 +1861,7 @@
         if (Math.abs(velocity) < mMinFlingVelocity) {
             // If the velocity is low enough, then treat this more as an automatic page advance
             // as opposed to an apparent physical response to flinging
-            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+            snapToPage(whichPage, getPageSnapDuration());
             return;
         }
 
@@ -1851,10 +1885,10 @@
     }
 
     protected void snapToPage(int whichPage) {
-        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+        snapToPage(whichPage, getPageSnapDuration());
     }
     protected void snapToPageImmediately(int whichPage) {
-        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+        snapToPage(whichPage, getPageSnapDuration(), true);
     }
 
     protected void snapToPage(int whichPage, int duration) {
@@ -1884,8 +1918,8 @@
             mNextPage = whichPage;
         }
 
-        if (mPageWarpIndex != -1) {
-            animateWarpPageOffScreen();
+        if (isWarping()) {
+            onPageEndWarp();
             resetPageWarp();
         }
 
@@ -1918,6 +1952,10 @@
         invalidate();
     }
 
+    protected boolean isWarping() {
+        return mPageWarpIndex != -1;
+    }
+
     public void scrollLeft() {
         if (mScroller.isFinished()) {
             if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
@@ -2650,21 +2688,72 @@
         mIsCameraEvent = false;
     }
 
-    private void animateWarpPageOnScreen() {
-        if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen()");
-        if (mPageWarpIndex != -1) {
+    AnimatorListenerAdapter mOnScreenAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mWarpAnimation = null;
+            if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_STATE_READY) {
+                animateWarpPageOffScreen("onScreen end", true);
+            }
+        }
+    };
+
+    AnimatorListenerAdapter mOffScreenAnimationListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mWarpAnimation = null;
+            mWarpPageExposed = true;
+        }
+    };
+
+    private void cancelWarpAnimation(String msg, boolean abortAnimation) {
+        if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ")");
+        if (abortAnimation) {
+            // We're done with the animation and moving to a new page.  Let the scroller
+            // take over the animation.
             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
-            if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
-            v.animate().translationX(mWarpPeekAmount).setDuration(WARP_PEEK_ANIMATION_DURATION);
+            v.animate().cancel();
+            // Make the scroll amount match the current warp position.
+            scrollBy(Math.round(-v.getTranslationX()), 0);
+            v.setTranslationX(0);
+        } else {
+            animateWarpPageOffScreen("canceled", true);
         }
     }
 
-    private void animateWarpPageOffScreen() {
-        if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen()");
-        if (mPageWarpIndex != -1) {
+    private boolean isAnimatingWarpPage() {
+        return mWarpAnimation != null;
+    }
+
+    private void animateWarpPageOnScreen(String reason) {
+        if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")");
+        if (isWarping()) {
+            mWarpPageExposed = true;
+            onPageBeginWarp();
+            KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
+            if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
+            DecelerateInterpolator interp = new DecelerateInterpolator(1.5f);
+            mWarpAnimation = v.animate();
+            mWarpAnimation.translationX(mWarpPeekAmount)
+                    .setInterpolator(interp)
+                    .setDuration(WARP_PEEK_ANIMATION_DURATION)
+                    .setListener(mOnScreenAnimationListener);
+        }
+    }
+
+    private void animateWarpPageOffScreen(String reason, boolean animate) {
+        if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")");
+        if (isWarping()) {
+            onPageEndWarp();
             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
             if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX());
-            v.animate().translationX(0.0f).setDuration(WARP_PEEK_ANIMATION_DURATION);
+            AccelerateInterpolator interp = new AccelerateInterpolator(1.5f);
+            v.animate().translationX(0.0f)
+                    .setInterpolator(interp)
+                    .setDuration(animate ? WARP_PEEK_ANIMATION_DURATION : 0)
+                    .setListener(mOffScreenAnimationListener);
+        } else {
+            if (DEBUG_WARP) Log.e(TAG, "animateWarpPageOffScreen(): not warping", new Exception());
         }
     }
 
@@ -2681,7 +2770,7 @@
         }
     }
 
-    public void startWarp(int pageIndex) {
+    public void startPageWarp(int pageIndex) {
         if (DEBUG_WARP) Log.v(TAG, "START WARP");
         if (pageIndex != mCurrentPage + 1) {
             mPageSwapIndex = mCurrentPage + 1;
@@ -2693,7 +2782,7 @@
         return mPageWarpIndex;
     }
 
-    public void endWarp() {
+    public void stopPageWarp() {
         if (DEBUG_WARP) Log.v(TAG, "END WARP");
         // mPageSwapIndex is reset in snapToPage() after the scroll animation completes
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
index 5e7816c..7a9a1c8 100644
--- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
@@ -88,8 +88,8 @@
     public static final int SCROLL_STATE_SETTLING = 2;
     public static final int SCROLL_STATE_FADING = 3;
 
-    private static final int CHALLENGE_FADE_OUT_DURATION = 100;
-    private static final int CHALLENGE_FADE_IN_DURATION = 160;
+    public static final int CHALLENGE_FADE_OUT_DURATION = 100;
+    public static final int CHALLENGE_FADE_IN_DURATION = 160;
 
     private static final int MAX_SETTLE_DURATION = 600; // ms
 
@@ -487,6 +487,7 @@
     @Override
     public void showBouncer() {
         if (mIsBouncing) return;
+        setSystemUiVisibility(getSystemUiVisibility() | STATUS_BAR_DISABLE_SEARCH);
         mWasChallengeShowing = mChallengeShowing;
         mIsBouncing = true;
         showChallenge(true);
@@ -513,6 +514,7 @@
     @Override
     public void hideBouncer() {
         if (!mIsBouncing) return;
+        setSystemUiVisibility(getSystemUiVisibility() & ~STATUS_BAR_DISABLE_SEARCH);
         if (!mWasChallengeShowing) showChallenge(false);
         mIsBouncing = false;
 
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 1e6954e..e1d0aec 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -41,13 +41,12 @@
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"/>
 
-    <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="18"/>
-
     <application
             android:allowClearUserData="true"
             android:label="@string/app_label"
             android:allowBackup= "false"
-            android:supportsRtl="true">
+            android:supportsRtl="true"
+            android:icon="@*android:drawable/ic_print">
 
         <service
             android:name=".PrintSpoolerService"
@@ -58,8 +57,13 @@
         <activity
             android:name=".PrintJobConfigActivity"
             android:configChanges="orientation|screenSize"
-            android:exported="false"
+            android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
             android:theme="@style/PrintJobConfigActivityTheme">
+            <intent-filter>
+                <action android:name="android.print.PRINT_DIALOG" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="printjob" android:pathPattern="*" />
+            </intent-filter>
         </activity>
 
         <activity
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_menu_print.png b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_print.png
new file mode 100644
index 0000000..09ab1a2
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_print.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_print.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_print.png
new file mode 100644
index 0000000..637d94e
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_print.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_print.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_print.png
new file mode 100644
index 0000000..4d4b3cc
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_print.png
Binary files differ
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
index 98b5cfe..d503216 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
@@ -15,9 +15,12 @@
 -->
 
 <com.android.printspooler.PrintDialogFrame xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/content_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:background="@color/container_background">
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+    <FrameLayout
+        android:id="@+id/content_container"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:background="@color/container_background">
+    </FrameLayout>
 </com.android.printspooler.PrintDialogFrame>
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
index 83019b9..02740a3 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml
@@ -107,7 +107,7 @@
                     android:layout_marginStart="36dip"
                     android:textAppearance="@style/PrintOptionTitleTextAppearance"
                     android:labelFor="@+id/range_options_spinner"
-                    android:text="@string/label_pages">
+                    android:text="@string/page_count_unknown">
                 </TextView>
 
                 <Spinner
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
new file mode 100644
index 0000000..222b5b6
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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:id="@+id/content_generating"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:background="@color/editable_background"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dip"
+            android:layout_marginEnd="16dip"
+            android:layout_marginTop="32dip"
+            android:layout_marginBottom="32dip"
+            android:layout_gravity="center"
+            style="?android:attr/buttonBarButtonStyle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:text="@string/print_error_default_message"
+            android:textColor="@color/important_text"
+            android:textSize="16sp">
+        </TextView>
+
+        <View
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:background="@color/separator">
+        </View>
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/ok_button"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="fill_horizontal"
+        style="?android:attr/buttonBarButtonStyle"
+        android:text="@android:string/ok"
+        android:textSize="16sp"
+        android:textColor="@color/important_text">
+    </Button>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index 2749aa6..1a61b99 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -15,13 +15,13 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:paddingStart="16dip"
-        android:paddingEnd="16dip"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:orientation="horizontal"
-        android:gravity="start|center_vertical">
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:paddingStart="16dip"
+      android:paddingEnd="16dip"
+      android:minHeight="?android:attr/listPreferredItemHeightSmall"
+      android:orientation="horizontal"
+      android:gravity="start|center_vertical">
 
     <ImageView
         android:id="@+id/icon"
@@ -31,7 +31,7 @@
         android:layout_marginEnd="8dip"
         android:duplicateParentState="true"
         android:contentDescription="@null"
-        android:visibility="gone">
+        android:visibility="invisible">
     </ImageView>
 
     <LinearLayout
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
new file mode 100644
index 0000000..47eb0b5
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:orientation="horizontal"
+        android:gravity="start|center_vertical">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="8dip"
+        android:duplicateParentState="true"
+        android:contentDescription="@null"
+        android:visibility="gone">
+    </ImageView>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:duplicateParentState="true">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textIsSelectable="false"
+            android:gravity="top|start"
+            android:textColor="@color/item_text_color"
+            android:duplicateParentState="true">
+        </TextView>
+
+        <TextView
+            android:id="@+id/subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textIsSelectable="false"
+            android:visibility="gone"
+            android:textColor="@color/print_option_title"
+            android:duplicateParentState="true">
+        </TextView>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_fragment.xml b/packages/PrintSpooler/res/layout/select_printer_fragment.xml
new file mode 100644
index 0000000..bbd012e
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/select_printer_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/list"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:paddingStart="@dimen/printer_list_view_padding_start"
+    android:paddingEnd="@dimen/printer_list_view_padding_end"
+    android:scrollbarStyle="outsideOverlay"
+    android:cacheColorHint="@android:color/transparent"
+    android:scrollbarAlwaysDrawVerticalTrack="true" >
+</ListView>
diff --git a/packages/PrintSpooler/res/values-af/strings.xml b/packages/PrintSpooler/res/values-af/strings.xml
index f307f2a..a6ad391 100644
--- a/packages/PrintSpooler/res/values-af/strings.xml
+++ b/packages/PrintSpooler/res/values-af/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Kleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oriëntasie"</string>
     <string name="label_pages" msgid="6300874667546617333">"Bladsye (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"bv. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"bv. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Drukvoorskou"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installeer PDF-bekyker vir voorskou"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Drukkerprogram het omgeval"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Genereer uitdruktaak"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Stoor as PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alle drukkers…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Drukdialoog"</string>
     <string name="search" msgid="5421724265322228497">"Deursoek"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alle drukkers"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Voeg diens by"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Soekkassie vertoon"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Soekkassie weggesteek"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Voeg drukker by"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> drukker gekry"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> drukkers gekry"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Kies drukdiens"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Soek tans vir drukkers"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Geen drukkers gekry nie"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Druk tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kanselleer tans <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Drukkerfout by <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Drukker het <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> geblokkeer"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>-uitdruktaak"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>-uitdruktake"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Kanselleer"</string>
     <string name="restart" msgid="2472034227037808749">"Herbegin"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met drukker nie"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie beskikbaar nie"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kon nie druktaak genereer nie"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Swart en wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
diff --git a/packages/PrintSpooler/res/values-am/strings.xml b/packages/PrintSpooler/res/values-am/strings.xml
index 49928750..6e5c00f 100644
--- a/packages/PrintSpooler/res/values-am/strings.xml
+++ b/packages/PrintSpooler/res/values-am/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"ቀለም"</string>
     <string name="label_orientation" msgid="2853142581990496477">"አቀማመጠ ገፅ"</string>
     <string name="label_pages" msgid="6300874667546617333">"ገጾች (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ለምሳሌ፦ 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ለምሳሌ፦ 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"የህትመት ቅድመ እይታ"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"ለቅድመ-እይታ የፒ ዲ ኤፍ መመልከቻ ይጫኑ"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"የአታሚ መተግበሪያ ተበላሽቷል"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"ገፆች"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"የህትመት ስራን በማመንጨት ላይ"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"እንደ ፒ ዲ ኤፍ አስቀምጥ"</string>
     <string name="all_printers" msgid="5018829726861876202">"ሁሉም አታሚዎች…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"የህትመት መገናኛ"</string>
     <string name="search" msgid="5421724265322228497">"ፍለጋ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ሁሉም አታሚዎች"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"አገልግሎት አክል"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"የፍለጋ ሳጥን ይታያል"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"የፍለጋ ሳጥን ተደብቋል"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"አታሚ አክል"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> አታሚ ተገኝቷል"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> አታሚዎች ተገኝተዋል"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"የህትመት አገልግሎት ይምረጡ"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"አታሚዎችን በመፈለግ ላይ"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"ምንም አታሚዎች አልተገኙም"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በማተም ላይ"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን በመተው ላይ"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"የአታሚ ስህተት <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"አታሚ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>ን አግዷል"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> የህትመት ስራ"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> የህትመት ስራዎች"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"ሰርዝ"</string>
     <string name="restart" msgid="2472034227037808749">"እንደገና ጀምር"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ከአታሚ ጋር ምንም ግንኙነት የለም"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"አይታወቅም"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – አይገኝም"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"የህትመት ስራን ማመንጨት አልተቻለም"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ጥቁር እና ነጭ"</item>
     <item msgid="2762241247228983754">"ቀለም"</item>
diff --git a/packages/PrintSpooler/res/values-ar/strings.xml b/packages/PrintSpooler/res/values-ar/strings.xml
index 03f62b8..1740833 100644
--- a/packages/PrintSpooler/res/values-ar/strings.xml
+++ b/packages/PrintSpooler/res/values-ar/strings.xml
@@ -25,49 +25,49 @@
     <string name="label_color" msgid="1108690305218188969">"اللون"</string>
     <string name="label_orientation" msgid="2853142581990496477">"الاتجاه"</string>
     <string name="label_pages" msgid="6300874667546617333">"الصفحات (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"مثل 1–5، 8، 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"على سبيل المثال، 1—5،8،11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"معاينة قبل الطباعة"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"تثبيت برنامج عرض PDF للمعاينة"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"تعطّل تطبيق الطباعة"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"الصفحات"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"جارٍ إنشاء مهمة الطباعة"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"حفظ بتنسيق PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"جميع الطابعات…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"مربع حوار الطباعة"</string>
     <string name="search" msgid="5421724265322228497">"بحث"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"جميع الطابعات"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"إضافة خدمة"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"تم إظهار مربع البحث"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"تم إخفاء مربع البحث"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"إضافة طابعة"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"تم العثور على <xliff:g id="COUNT">%1$s</xliff:g> طابعة"</item>
+    <item quantity="other" msgid="6533817036607128241">"تم العثور على <xliff:g id="COUNT">%1$s</xliff:g> من الطابعات"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"اختر خدمة طباعة"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"البحث عن طابعات"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"لم يتم العثور على طابعات"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"جارٍ طباعة <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"جارٍ إلغاء <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"خطا في الطابعة <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"رفضت الطابعة <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> مهمة طباعة"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> من مهام الطباعة"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"إلغاء"</string>
     <string name="restart" msgid="2472034227037808749">"إعادة تشغيل"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"لا يوجد اتصال بالطابعة"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"غير معروف"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – غير متاحة"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"تعذر إنشاء عملية الطباعة"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"أبيض وأسود"</item>
     <item msgid="2762241247228983754">"اللون"</item>
   </string-array>
   <string-array name="orientation_labels">
-    <item msgid="4061931020926489228">"صورة أشخاص"</item>
-    <item msgid="3199660090246166812">"معالم"</item>
+    <item msgid="4061931020926489228">"عمودي"</item>
+    <item msgid="3199660090246166812">"أفقية"</item>
   </string-array>
   <string-array name="page_options_labels">
     <item msgid="7421377442011699994">"الكل"</item>
diff --git a/packages/PrintSpooler/res/values-bg/strings.xml b/packages/PrintSpooler/res/values-bg/strings.xml
index a4e8f53..1573b3b 100644
--- a/packages/PrintSpooler/res/values-bg/strings.xml
+++ b/packages/PrintSpooler/res/values-bg/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Цвят"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="6300874667546617333">"Страници (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"напр. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"напр. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Визуализация за печат"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Инсталиране на визуализатор на PDF"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Получи се срив в приложението за отпечатване"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Страници"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Заданието за печат се генерира"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Запазване като PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Всички принтери…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Диалогов прозорец за отпечатване"</string>
     <string name="search" msgid="5421724265322228497">"Търсене"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Всички принтери"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Добавяне на услуга"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Полето за търсене е показано"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Полето за търсене е скрито"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Добавяне на принтер"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Намерен е <xliff:g id="COUNT">%1$s</xliff:g> принтер"</item>
+    <item quantity="other" msgid="6533817036607128241">"Намерени са <xliff:g id="COUNT">%1$s</xliff:g> принтера"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Избиране на услуга за отпечатване"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Търсят се принтери"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Няма намерени принтери"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се отпечатва"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ се анулира"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Грешка в принтера при „<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Принтерът блокира при „<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Задание за отпечатване: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Задания за отпечатване: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Отказ"</string>
     <string name="restart" msgid="2472034227037808749">"Рестартиране"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Няма връзка с принтера"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"няма данни"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – не е налице"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Заданието за отпечатване не можа да се генерира"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-бяло"</item>
     <item msgid="2762241247228983754">"Цветно"</item>
diff --git a/packages/PrintSpooler/res/values-ca/donottranslate.xml b/packages/PrintSpooler/res/values-ca/donottranslate.xml
new file mode 100644
index 0000000..7537aa5
--- /dev/null
+++ b/packages/PrintSpooler/res/values-ca/donottranslate.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="mediasize_default">NA_LETTER</string>
+    <string name="mediasize_standard">@string/mediasize_standard_north_america</string>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index d7cc213..3ad5892 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientació"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pàgines (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"p. ex. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Visualització prèvia impressió"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instal·la un lector de PDF per a visualitz. prèvia"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"L\'aplicació d\'impressió ha fallat"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Pàgines"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generant tasca impressió"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Desa com a PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Totes les impressores…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Diàleg d\'impressió"</string>
     <string name="search" msgid="5421724265322228497">"Cerca"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Totes les impressores"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Afegeix un servei"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Es mostra el quadre de cerca"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"S\'ha amagat el quadre de cerca"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Afegeix una impressora"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"S\'ha trobat <xliff:g id="COUNT">%1$s</xliff:g> impressora"</item>
+    <item quantity="other" msgid="6533817036607128241">"S\'han trobat <xliff:g id="COUNT">%1$s</xliff:g> impressores"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Selecció del servei d\'impressió"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Cerca d\'impressores"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"No s\'ha trobat cap impressora"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"S\'està imprimint <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"S\'està cancel·lant <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Error d\'impressora <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Impressora bloquejada <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tasca d\'impressió per a <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tasques d\'impressió per a <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancel·la"</string>
     <string name="restart" msgid="2472034227037808749">"Reinicia"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hi ha connexió amb la impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconegut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"No s\'ha pogut generar la tasca d\'impressió"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanc i negre"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index f435215..8f82879 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Barva"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientace"</string>
     <string name="label_pages" msgid="6300874667546617333">"STRÁNKY (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"např. 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"např. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Náhled tisku"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Nainstalovat prohlížeč PDF (umožní náhled)"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikace tisku selhala"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Generování úlohy tisku"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Uložit ve formátu PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Všechny tiskárny…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dialog tisku"</string>
     <string name="search" msgid="5421724265322228497">"Hledat"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Všechny tiskárny"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Přidat službu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Vyhledávací pole se zobrazuje"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Vyhledávací pole je skryto"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Přidat tiskárnu"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Počet nalezených tiskáren: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+    <item quantity="other" msgid="6533817036607128241">"Počet nalezených tiskáren: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Zvolte službu tisku"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhledávání tiskáren"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Nebyly nalezeny žádné tiskárny"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tisk úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Rušení úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Chyba tiskárny u úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Tiskárna blokuje úlohu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Počet tiskových úloh: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Počet tiskových úloh: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Zrušit"</string>
     <string name="restart" msgid="2472034227037808749">"Restartovat"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nelze se připojit k tiskárně"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznámé"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – není k dispozici"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskovou úlohu nelze vytvořit"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Černobíle"</item>
     <item msgid="2762241247228983754">"Barevně"</item>
diff --git a/packages/PrintSpooler/res/values-da/strings.xml b/packages/PrintSpooler/res/values-da/strings.xml
index c0dcee4..9248cba 100644
--- a/packages/PrintSpooler/res/values-da/strings.xml
+++ b/packages/PrintSpooler/res/values-da/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Farve"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sider (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"f.eks. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"f.eks. 1-5,8,11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Vis udskrift"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installer et PDF-visningsprog. for at se eksempel"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Udskrivningsapp gik ned"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Sider"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Udskriften generes"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Gem som PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alle printere..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Udskriftsdialog"</string>
     <string name="search" msgid="5421724265322228497">"Søg"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alle printere"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Tilføj tjeneste"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Søgefeltet vises"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Søgefeltet er skjult"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Tilføj printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Der blev fundet <xliff:g id="COUNT">%1$s</xliff:g> printer"</item>
+    <item quantity="other" msgid="6533817036607128241">"Der blev fundet <xliff:g id="COUNT">%1$s</xliff:g> printere"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Vælg udskriftstjeneste"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Søger efter printere"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Der blev ikke fundet nogen printere"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> udskrives"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annulleres"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Udskriften <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> mislykkedes"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printeren har blokeret <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>-udskriftsjob"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>-udskriftsjobs"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Annuller"</string>
     <string name="restart" msgid="2472034227037808749">"Genstart"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse til printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukendt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ikke tilgængelig"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Der kunne ikke genereres et udskriftsjob"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Sort/hvid"</item>
     <item msgid="2762241247228983754">"Farve"</item>
diff --git a/packages/PrintSpooler/res/values-de/strings.xml b/packages/PrintSpooler/res/values-de/strings.xml
index 97c8c0e..96c601a 100644
--- a/packages/PrintSpooler/res/values-de/strings.xml
+++ b/packages/PrintSpooler/res/values-de/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Farbe"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ausrichtung"</string>
     <string name="label_pages" msgid="6300874667546617333">"Seiten (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"z. B. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"z. B. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Vorschau drucken"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"PDF-Viewer für Vorschau installieren"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Druck-App abgestürzt"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Druckauftrag wird generiert..."</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Als PDF speichern"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alle Drucker…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Druckdialogfeld"</string>
     <string name="search" msgid="5421724265322228497">"Suchen"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alle Drucker"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Dienst hinzufügen"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Suchfeld angezeigt"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Suchfeld ausgeblendet"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Drucker hinzufügen"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> Drucker gefunden"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> Drucker gefunden"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Druckdienst auswählen"</string>
-    <string name="print_searching_for_printers" msgid="6550424555079932867">"Suche nach Druckern…"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Suche nach Druckern"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Keine Drucker gefunden"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird gedruckt..."</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> wird abgebrochen..."</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Druckerfehler <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Drucker hat <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> blockiert."</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Druckauftrag \"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>\""</item>
+    <item quantity="other" msgid="8746611264734222865">"Druckaufträge \"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>\""</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Abbrechen"</string>
     <string name="restart" msgid="2472034227037808749">"Neu starten"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Keine Verbindung zum Drucker"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unbekannt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nicht verfügbar"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Druckauftrag konnte nicht generiert werden."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Schwarz-weiß"</item>
     <item msgid="2762241247228983754">"Farbe"</item>
diff --git a/packages/PrintSpooler/res/values-el/strings.xml b/packages/PrintSpooler/res/values-el/strings.xml
index 6bdf19d..2c5142d 100644
--- a/packages/PrintSpooler/res/values-el/strings.xml
+++ b/packages/PrintSpooler/res/values-el/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Χρώμα"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Προσανατολισμός"</string>
     <string name="label_pages" msgid="6300874667546617333">"Σελίδες (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"π.χ. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"π.χ. 1-5,8,11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Προεπισκόπηση εκτύπωσης"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Εγκαταστήστε το PDF viewer για προεπισκόπηση"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Διακοπή λειτουργίας εφαρμογής εκτύπωσης"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Σελίδες"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Δημιουργία εργασίας εκτύπωσης"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Αποθήκευση ως PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Όλοι οι εκτυπωτές…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Πλαίσιο διαλόγου εκτύπωσης"</string>
     <string name="search" msgid="5421724265322228497">"Αναζήτηση"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Όλοι οι εκτυπωτές"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Προσθήκη υπηρεσίας"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Εμφάνιση πλαισίου αναζήτησης"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Απόκρυψη πλαισίου αναζήτησης"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Προσθήκη εκτυπωτή"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Βρέθηκε <xliff:g id="COUNT">%1$s</xliff:g> εκτυπωτής"</item>
+    <item quantity="other" msgid="6533817036607128241">"Βρέθηκαν <xliff:g id="COUNT">%1$s</xliff:g> εκτυπωτές"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Επιλέξτε υπηρεσία εκτύπωσης"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Αναζήτηση για εκτυπωτές"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Δεν βρέθηκαν εκτυπωτές"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Εκτύπωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ακύρωση <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Σφάλμα εκτυπωτή <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Ο εκτυπωτής απέκλεισε <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"εργασία εκτύπωσης <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"εργασίες εκτύπωσης <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Ακύρωση"</string>
     <string name="restart" msgid="2472034227037808749">"Επανεκκίνηση"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Δεν υπάρχει σύνδεση με εκτυπωτή"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"άγνωστο"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – μη διαθέσιμο"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Δεν ήταν δυνατή η δημιουργία εργασίας εκτύπωσης"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Ασπρόμαυρο"</item>
     <item msgid="2762241247228983754">"Χρώμα"</item>
diff --git a/packages/PrintSpooler/res/values-en-rGB/strings.xml b/packages/PrintSpooler/res/values-en-rGB/strings.xml
index 77f275d..5582c7a 100644
--- a/packages/PrintSpooler/res/values-en-rGB/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rGB/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Colour"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"e.g. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Print preview"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Install PDF viewer for preview"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Printing app crashed"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Generating print job"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Save as PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"All printers…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Print dialogue"</string>
     <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"All printers"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Add service"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Search box shown"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Search box hidden"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Add printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> printer found"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> printers found"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printer error <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printer blocked <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> print job"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> print jobs"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancel"</string>
     <string name="restart" msgid="2472034227037808749">"Restart"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
diff --git a/packages/PrintSpooler/res/values-en-rIN/strings.xml b/packages/PrintSpooler/res/values-en-rIN/strings.xml
index 77f275d..5582c7a 100644
--- a/packages/PrintSpooler/res/values-en-rIN/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rIN/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Colour"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"e.g. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Print preview"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Install PDF viewer for preview"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Printing app crashed"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Generating print job"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Save as PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"All printers…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Print dialogue"</string>
     <string name="search" msgid="5421724265322228497">"Search"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"All printers"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Add service"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Search box shown"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Search box hidden"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Add printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> printer found"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> printers found"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Choose print service"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Searching for printers"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"No printers found"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Printing <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelling <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printer error <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printer blocked <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> print job"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> print jobs"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancel"</string>
     <string name="restart" msgid="2472034227037808749">"Restart"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No connection to printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"unknown"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – unavailable"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Couldn\'t generate print job"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Colour"</item>
diff --git a/packages/PrintSpooler/res/values-es-rUS/strings.xml b/packages/PrintSpooler/res/values-es-rUS/strings.xml
index c3cc9cb..891ccf2 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"p. ej.: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"Ej.: 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Vista previa de impresión"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalar visualizador de PDF para vista previa"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"La aplicación de impresión falló"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Páginas"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generando trabajo de impresión"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Guardar como PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Todas las impresoras…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Cuadro de diálogo de impresión"</string>
     <string name="search" msgid="5421724265322228497">"Buscar"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Todas las impresoras"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Agregar servicio"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Cuadro de búsqueda visible"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Cuadro de búsqueda oculto"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Agregar impresora"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Se encontró <xliff:g id="COUNT">%1$s</xliff:g> impresora."</item>
+    <item quantity="other" msgid="6533817036607128241">"Se encontraron <xliff:g id="COUNT">%1$s</xliff:g> impresoras."</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Elegir servicio de impresión"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Error de impresora <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"La impresora bloqueó <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>."</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Trabajo de impresión <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Trabajos de impresión <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancelar"</string>
     <string name="restart" msgid="2472034227037808749">"Reiniciar"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>: no disponible"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 4cd820f..052e06c 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientación"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"p.ej.: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"p. ej.: 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Vista previa de impresión"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalar visor PDF para obtener vista previa"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Error de aplicación de impresión"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Generando trabajo de impresión"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Guardar como PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Todas las impresoras…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Cuadro de diálogo de impresión"</string>
     <string name="search" msgid="5421724265322228497">"Buscar"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Todas las impresoras"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Añadir servicio"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Cuadro de búsqueda visible"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Cuadro de búsqueda oculto"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Añadir impresora"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Se ha encontrado <xliff:g id="COUNT">%1$s</xliff:g> impresora"</item>
+    <item quantity="other" msgid="6533817036607128241">"Se han encontrado <xliff:g id="COUNT">%1$s</xliff:g> impresoras"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Seleccionar servicio de impresión"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Buscando impresoras"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"No se encontraron impresoras"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimiendo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Error de impresora <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"La impresora ha bloqueado <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Trabajo de impresión <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Trabajos de impresión <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancelar"</string>
     <string name="restart" msgid="2472034227037808749">"Volver a empezar"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"No hay conexión con la impresora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconocido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – no disponible"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Error al generar el trabajo de impresión"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Blanco y negro"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-et-rEE/strings.xml b/packages/PrintSpooler/res/values-et-rEE/strings.xml
index b2e579f..814179d 100644
--- a/packages/PrintSpooler/res/values-et-rEE/strings.xml
+++ b/packages/PrintSpooler/res/values-et-rEE/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Värv"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suund"</string>
     <string name="label_pages" msgid="6300874667546617333">"Lehti (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"nt 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"nt 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Prindi eelvaade"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"PDF-vaaturi installimine eelvaate kuvamiseks"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Printimisrakendus jooksis kokku"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Prinditöö loomine"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Salvesta PDF-ina"</string>
     <string name="all_printers" msgid="5018829726861876202">"Kõik printerid …"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Printimisdialoog"</string>
     <string name="search" msgid="5421724265322228497">"Otsing"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Kõik printerid"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Lisa teenus"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Otsingukast on kuvatud"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Otsingukast on peidetud"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Lisa printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Leiti <xliff:g id="COUNT">%1$s</xliff:g> printer"</item>
+    <item quantity="other" msgid="6533817036607128241">"Leiti <xliff:g id="COUNT">%1$s</xliff:g> printerit"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Prinditeenuse valimine"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Printerite otsimine"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Printereid ei leitud"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> printimine"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> tühistamine"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printeri viga: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printer blokeeris töö <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Prinditöö <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Prinditööd <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Tühista"</string>
     <string name="restart" msgid="2472034227037808749">"Taaskäivita"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Printeriühendus puudub"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"teadmata"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – pole saadaval"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Prinditööd ei saanud luua"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustvalge"</item>
     <item msgid="2762241247228983754">"Värv"</item>
diff --git a/packages/PrintSpooler/res/values-fa/strings.xml b/packages/PrintSpooler/res/values-fa/strings.xml
index 1846398..2bc3623 100644
--- a/packages/PrintSpooler/res/values-fa/strings.xml
+++ b/packages/PrintSpooler/res/values-fa/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"رنگی"</string>
     <string name="label_orientation" msgid="2853142581990496477">"جهت"</string>
     <string name="label_pages" msgid="6300874667546617333">"صفحات (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"به عنوان مثال، ۵-۱، ۸، ۱۳-۱۱"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"‏‏‎مثلاً ۱—۵،‏۹،۷—۱۰"</string>
     <string name="print_preview" msgid="8010217796057763343">"پیش‌نمایش چاپ"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"نصب نمایشگر PDF برای پیش‌نمایش"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"برنامه چاپ خراب شد"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"صفحات"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"در حال ایجاد کار چاپ"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"ذخیره به‌عنوان PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"همه چاپگرها..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"چاپ گفتگو"</string>
     <string name="search" msgid="5421724265322228497">"جستجو"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"همه چاپگرها"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"افزودن سرویس"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"کادر جستجو نمایان شد"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"کادر جستجو پنهان شد"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"افزودن چاپگر"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> چاپگر یافت شد"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> چاپگر یافت شد"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"انتخاب سرویس چاپ"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"در حال جستجو برای چاپگرها"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"هیچ چاپگری یافت نشد"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"در حال چاپ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"در حال لغو <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"خطای چاپگر <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"چاپگر، کار <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> را مسدود کرد"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"کار چاپ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"کارهای چاپ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"لغو"</string>
     <string name="restart" msgid="2472034227037808749">"راه‌اندازی مجدد"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"اتصال با چاپگر برقرار نیست"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"نامعلوم"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - در دسترس نیست"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"کار چاپ ایجاد نشد"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"سیاه و سفید"</item>
     <item msgid="2762241247228983754">"رنگی"</item>
diff --git a/packages/PrintSpooler/res/values-fi/strings.xml b/packages/PrintSpooler/res/values-fi/strings.xml
index d6324a6..1a7caa9 100644
--- a/packages/PrintSpooler/res/values-fi/strings.xml
+++ b/packages/PrintSpooler/res/values-fi/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Väri"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Suunta"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sivut (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"esim. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"esim. 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Tulostuksen esikatselu"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Asenna PDF-katseluohjelma esikatselua varten"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Tulostussovellus kaatui"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Luodaan tulostustyö"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Tallenna PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Kaikki tulostimet…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Tulostusikkuna"</string>
     <string name="search" msgid="5421724265322228497">"Haku"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Kaikki tulostimet"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Lisää palvelu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Hakukenttä näkyvissä"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Hakukenttä piilotettu"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Lisää tulostin"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Löytyi <xliff:g id="COUNT">%1$s</xliff:g> tulostin"</item>
+    <item quantity="other" msgid="6533817036607128241">"Löytyi <xliff:g id="COUNT">%1$s</xliff:g> tulostinta"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Valitse tulostuspalvelu"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Etsitään tulostimia"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Tulostimia ei löydy"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tulostetaan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Peruutetaan työ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Tulostinvirhe työlle <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Tulostin esti työn <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tulostustyö <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tulostustyöt <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Peruuta"</string>
     <string name="restart" msgid="2472034227037808749">"Käynnistä uudelleen"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ei yhteyttä tulostimeen"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tuntematon"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ei käytettävissä"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tulostustyötä ei voitu luoda"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Mustavalkoinen"</item>
     <item msgid="2762241247228983754">"Väri"</item>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index 06b3096..581f2a6 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Couleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"p.ex. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Aperçu avant impression"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installer un lecteur PDF pour voir l\'aperçu"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"L\'application à l\'origine de l\'impression a planté"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Pages"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Génération tâche impression…"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Enregistrer en format PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Toutes les imprimantes…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Boîte de dialogue d\'impression"</string>
     <string name="search" msgid="5421724265322228497">"Rechercher"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Toutes les imprimantes"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Ajouter le service"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Champ de recherche affiché"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Champ de recherche masqué"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Ajouter une imprimante"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> imprimante trouvée"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> imprimantes trouvées"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours..."</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Impression de <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> en cours…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de « <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> »…"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Erreur impression : « <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> »"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Impression de « <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> » bloquée"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tâche d\'impression <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tâches d\'impression <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Annuler"</string>
     <string name="restart" msgid="2472034227037808749">"Recommencer"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnu"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — indisponible"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 38d7cf6..247a21b 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Couleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pages (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ex. : 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ex. : 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Aperçu avant impression"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installer un lecteur PDF pour afficher l\'aperçu"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"L\'application à l\'origine de l\'impression a planté"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Génération tâche impression…"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Enregistrer au format .PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Toutes les imprimantes…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Boîte de dialogue d\'impression"</string>
     <string name="search" msgid="5421724265322228497">"Rechercher"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Toutes les imprimantes"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Ajouter un service"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Champ de recherche affiché."</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Champ de recherche masqué."</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Ajouter une imprimante"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> imprimante trouvée."</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> imprimantes trouvées."</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Sélectionner le service d\'impression"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Recherche d\'imprimantes en cours"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Aucune imprimante trouvée"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Impression de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annulation de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Erreur impression pour \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\""</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Impression de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" bloquée"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> tâche d\'impression"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> tâches d\'impression"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Annuler"</string>
     <string name="restart" msgid="2472034227037808749">"Redémarrer"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Aucune connexion à l\'imprimante."</string>
     <string name="reason_unknown" msgid="5507940196503246139">"inconnue"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponible"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossible de générer la tâche d\'impression."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Noir et blanc"</item>
     <item msgid="2762241247228983754">"Couleur"</item>
diff --git a/packages/PrintSpooler/res/values-hi/strings.xml b/packages/PrintSpooler/res/values-hi/strings.xml
index 36c8039..5b1e406 100644
--- a/packages/PrintSpooler/res/values-hi/strings.xml
+++ b/packages/PrintSpooler/res/values-hi/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"रंग"</string>
     <string name="label_orientation" msgid="2853142581990496477">"अभिविन्‍यास"</string>
     <string name="label_pages" msgid="6300874667546617333">"पृष्‍ठ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"उदा. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"उदा. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"प्रिंट पूर्वावलोकन"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकन के लिए PDF व्यूअर इंस्टॉल करें"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"प्रिंटिंग एप्लिकेशन क्रैश हो गया"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"प्रिंट कार्य जनरेट हो रहा है"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF के रूप में सहेजें"</string>
     <string name="all_printers" msgid="5018829726861876202">"सभी प्रिंटर..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"प्रिंट संवाद"</string>
     <string name="search" msgid="5421724265322228497">"खोजें"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"सभी प्रिंटर"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"सेवा जोड़ें"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"खोज बॉक्स प्रदर्शित है"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"खोज बॉक्स छिपा हुआ है"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"प्रिंटर जोड़ें"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> प्रिंटर मिला"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> प्रिंटर मिले"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"प्रिंट सेवा चुनें"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"प्रिंटर खोज रहा है"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"कोई प्रिंटर नहीं मिले"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> प्रिंट हो रहा है"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द हो रहा है"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"प्रिंटर त्रुटि <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"प्रिंटर अवरोधित <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> प्रिंट कार्य"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> प्रिंट कार्य"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"रद्द करें"</string>
     <string name="restart" msgid="2472034227037808749">"पुन: आरंभ करें"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिंटर के लिए कोई कनेक्शन नहीं"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – अनुपलब्ध"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"प्रिंट कार्य जनरेट नहीं किया जा सका"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"श्याम और श्वेत"</item>
     <item msgid="2762241247228983754">"रंग"</item>
diff --git a/packages/PrintSpooler/res/values-hr/strings.xml b/packages/PrintSpooler/res/values-hr/strings.xml
index ff22dfb..fe2ee7a 100644
--- a/packages/PrintSpooler/res/values-hr/strings.xml
+++ b/packages/PrintSpooler/res/values-hr/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"U boji"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orijentacija"</string>
     <string name="label_pages" msgid="6300874667546617333">"Stranice (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"npr., 1–5, 8"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"npr. 1 – 5,8,11 – 13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Pregled ispisa"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instaliraj PDF preglednik za pregled"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Srušila se aplikacija za ispis"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Stranice"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generiranje zadatka ispisa"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Spremi kao PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Svi pisači…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dijaloški okvir za ispis"</string>
     <string name="search" msgid="5421724265322228497">"Pretraživanje"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Svi pisači"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Dodaj uslugu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Okvir za pretraživanje prikazan je"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Okvir za pretraživanje skriven je"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Dodaj pisač"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Pronađen je <xliff:g id="COUNT">%1$s</xliff:g> pisač"</item>
+    <item quantity="other" msgid="6533817036607128241">"Pronađen je sljedeći broj pisača: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Odaberite uslugu ispisa"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Traženje pisača"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nije pronađen nijedan pisač"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Ispisivanje <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Otkazivanje zadatka <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Pogreška pisača <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Pisač je blokirao <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Zadatak ispisa <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Broj zadataka ispisa: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Odustani"</string>
     <string name="restart" msgid="2472034227037808749">"Ponovo pokreni"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nema veze s pisačem"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nepoznato"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – zadatak nije dostupan"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Zadatak ispisa nije generiran"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Crno-bijelo"</item>
     <item msgid="2762241247228983754">"U boji"</item>
diff --git a/packages/PrintSpooler/res/values-hu/strings.xml b/packages/PrintSpooler/res/values-hu/strings.xml
index 5dcedd8..49e103f 100644
--- a/packages/PrintSpooler/res/values-hu/strings.xml
+++ b/packages/PrintSpooler/res/values-hu/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Szín"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Tájolás"</string>
     <string name="label_pages" msgid="6300874667546617333">"Oldalszám (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"pl. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"pl. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Előnézet nyomtatása"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Az előnézethez telepítse a PDF-megtekintőt."</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"A nyomtatási alkalmazás összeomlott."</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Nyomtatási feladat létrehozása"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Mentés PDF-ként"</string>
     <string name="all_printers" msgid="5018829726861876202">"Az összes nyomtató…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Nyomtatási párbeszédablak"</string>
     <string name="search" msgid="5421724265322228497">"Keresés"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Az összes nyomtató"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Szolgáltatás hozzáadása"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Keresőmező megjelenítve"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Keresőmező elrejtve"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Nyomtató hozzáadása"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> nyomtató észlelve"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> nyomtató észlelve"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Nyomtatási szolgáltatás kiválasztása"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Nyomtatók keresése"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Nem található nyomtató"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> nyomtatása"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> törlése"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Nyomtatási hiba: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"A(z) <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> letiltva."</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> – nyomtatási feladat"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> – nyomtatási feladatok"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Mégse"</string>
     <string name="restart" msgid="2472034227037808749">"Újraindítás"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nincs kapcsolat a nyomtatóval"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ismeretlen"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nem érhető el"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nem sikerült létrehozni a nyomtatási feladatot."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Fekete-fehér"</item>
     <item msgid="2762241247228983754">"Szín"</item>
diff --git a/packages/PrintSpooler/res/values-hy-rAM/strings.xml b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
index 8e547f8..97fa171 100644
--- a/packages/PrintSpooler/res/values-hy-rAM/strings.xml
+++ b/packages/PrintSpooler/res/values-hy-rAM/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Գույնը"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Դիրքավորում"</string>
     <string name="label_pages" msgid="6300874667546617333">"Էջեր (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"օր.՝ 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"օր.՝ 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Տպելու նախադիտում"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Նախադիտման համար տեղադրեք PDF դիտարկիչ"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Տպելու ծրագիրը վթարի է ենթարկվել"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Ձևավորվում է տպելու աշխատանքը"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Պահել որպես PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Բոլոր տպիչները..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Տպելու երկխոսության պատուհան"</string>
     <string name="search" msgid="5421724265322228497">"Որոնել"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Բոլոր տպիչները"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Ավելացնել ծառայություն"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Որոնման վանդակը ցուցադրված է"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Որոնման վանդակը թաքցվել է"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Ավելացնել տպիչ"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> տպիչ է գտնվել"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> տպիչ է գտնվել"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Ընտրեք տպելու ծառայությունը"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Տպիչների որոնում"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Տպիչներ չեն գտնվել"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Տպվում է՝ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ը չեղարկվում է"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Տպիչի սխալ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Տպիչն արգելափակել է <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ը"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> տպման աշխատանք"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> տպման աշխատանքներ"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Չեղարկել"</string>
     <string name="restart" msgid="2472034227037808749">"Վերագործարկել"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Տպիչի հետ կապ չկա"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"անհայտ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> տպիչն անհասանելի է"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Չկարողացանք մշակել տպման աշխատանքը"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Սև ու սպիտակ"</item>
     <item msgid="2762241247228983754">"Գույնը"</item>
diff --git a/packages/PrintSpooler/res/values-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index 507f09e..a142aa3 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/strings.xml
@@ -25,40 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Warna"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) halaman"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"misalnya 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Pratinjau cetak"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Pasang penampil PDF untuk pratinjau"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikasi pencetakan mogok"</string>
-    <string name="page_count_unknown" msgid="6058852665954511124">"Laman"</string>
+    <string name="page_count_unknown" msgid="6058852665954511124">"Halaman"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Membuat tugas pencetakan"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Simpan sebagai PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Semua printer…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Cetak dialog"</string>
     <string name="search" msgid="5421724265322228497">"Cari"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Semua printer"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Tambahkan layanan"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Kotak telusur ditampilkan"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Kotak telusur disembunyikan"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Tambahkan printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> printer ditemukan"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> printer ditemukan"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pilih layanan cetak"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari printer"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Tidak ditemukan printer"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Ada kesalahan printer <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printer memblokir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tugas cetak <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tugas cetak <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Batal"</string>
     <string name="restart" msgid="2472034227037808749">"Mulai Ulang"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tidak ada sambungan ke printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat membuat tugas cetak"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index 4bb7c5b1..b63b2f4 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"A colori"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientamento"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagine (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"Es.: 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"Es.: 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Anteprima di stampa"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installa visualizzatore PDF per anteprima"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Arresto anomalo dell\'app di stampa"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Generazione processo di stampa"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Salva in PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Tutte le stampanti…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Finestra di dialogo Stampa"</string>
     <string name="search" msgid="5421724265322228497">"Cerca"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Tutte le stampanti"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Aggiungi servizio"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Casella di ricerca visualizzata"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Casella di ricerca nascosta"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Aggiungi stampante"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> stampante trovata"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> stampanti trovate"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Scegli servizio di stampa"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Ricerca di stampanti"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Nessuna stampante trovata"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Stampa di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Annullamento di <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Errore della stampante: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"La stampante ha bloccato <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Processo di stampa <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Processi di stampa <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Annulla"</string>
     <string name="restart" msgid="2472034227037808749">"Riavvia"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nessun collegamento alla stampante"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"sconosciuto"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - non disponibile"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Impossibile generare processo di stampa"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Bianco e nero"</item>
     <item msgid="2762241247228983754">"A colori"</item>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index 7521339..e6f8e42 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"צבע"</string>
     <string name="label_orientation" msgid="2853142581990496477">"כיוון"</string>
     <string name="label_pages" msgid="6300874667546617333">"עמודים (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"למשל 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"למשל 1–5‏,8,‏11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"תצוגה מקדימה של הדפסה"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"התקן מציג PDF ליצירת תצוגה מקדימה"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"אפליקציית ההדפסה קרסה"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"יוצר עבודת הדפסה"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"שמור כ-PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"כל המדפסות…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"תיבת דו שיח של מדפסת"</string>
     <string name="search" msgid="5421724265322228497">"חפש"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"כל המדפסות"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"הוסף שירות"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"תיבת החיפוש מוצגת"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"תיבת החיפוש מוסתרת"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"הוסף מדפסת"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"נמצאה מדפסת <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+    <item quantity="other" msgid="6533817036607128241">"נמצאו <xliff:g id="COUNT">%1$s</xliff:g> מדפסות"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"בחר שירות הדפסה"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"מחפש מדפסות"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"לא נמצאו מדפסות"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"מדפיס את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"מבטל את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"שגיאת מדפסת ב-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"המדפסת חסמה את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"עבודת הדפסה <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> עבודות הדפסה"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"בטל"</string>
     <string name="restart" msgid="2472034227037808749">"הפעל מחדש"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"אין חיבור למדפסת"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"לא ידוע"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – לא זמינה"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"לא ניתן היה ליצור את עבודת ההדפסה"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"שחור ולבן"</item>
     <item msgid="2762241247228983754">"צבע"</item>
diff --git a/packages/PrintSpooler/res/values-ja/strings.xml b/packages/PrintSpooler/res/values-ja/strings.xml
index 7556049..b9de967 100644
--- a/packages/PrintSpooler/res/values-ja/strings.xml
+++ b/packages/PrintSpooler/res/values-ja/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"色"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"ページ(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"例: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"例: 1-5,8,11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"印刷プレビュー"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"プレビュー用PDFビューアをインストール"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"印刷アプリでの障害発生"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"印刷ジョブを生成しています"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF形式で保存"</string>
     <string name="all_printers" msgid="5018829726861876202">"すべてのプリンタ…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"印刷ダイアログ"</string>
     <string name="search" msgid="5421724265322228497">"検索"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"すべてのプリンタ"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"サービスを追加"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"検索ボックスは表示されています"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"検索ボックスは表示されていません"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"プリンタを追加"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g>台のプリンタが見つかりました"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g>台のプリンタが見つかりました"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"印刷サービスの選択"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"プリンタの検索中"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"プリンタが見つかりません"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>を印刷しています"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>をキャンセルしています"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"プリンタエラー: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>をブロックしました"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>の印刷ジョブ"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>の印刷ジョブ"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"キャンセル"</string>
     <string name="restart" msgid="2472034227037808749">"再起動"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"プリンタに接続されていません"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>–使用不可"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"印刷ジョブを生成できませんでした"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"モノクロ"</item>
     <item msgid="2762241247228983754">"色"</item>
diff --git a/packages/PrintSpooler/res/values-ka-rGE/strings.xml b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
index 25abd07..87c6ce8 100644
--- a/packages/PrintSpooler/res/values-ka-rGE/strings.xml
+++ b/packages/PrintSpooler/res/values-ka-rGE/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"ფერი"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ორიენტაცია"</string>
     <string name="label_pages" msgid="6300874667546617333">"გვერდები (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"მაგ. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"მაგ. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"ნახვა ამობეჭდვამდე"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"გადახედვისთვის დააყენეთ PDF მნახველი"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"ბეჭდვის აპი ავარიულად გაითიშა"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"გვერდები"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"მიმდინარეობის ბეჭდვის დავალების შექმნა"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF-ად შენახვა"</string>
     <string name="all_printers" msgid="5018829726861876202">"ყველა პრინტერი…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"ბეჭდვის სარკმელი"</string>
     <string name="search" msgid="5421724265322228497">"ძიება"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ყველა პრინტერი"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"სერვისის დამატება"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"საძიებო ველი ნაჩვენებია"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"საძიებო ველი დამალულია"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"პრინტერის დამატება"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"ნაპოვნია <xliff:g id="COUNT">%1$s</xliff:g> პრინტერი"</item>
+    <item quantity="other" msgid="6533817036607128241">"ნაპოვნია <xliff:g id="COUNT">%1$s</xliff:g> პრინტერი"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"აირჩიეთ ბეჭდვის სერვისი"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"მიმდინარეობს პრინტერების ძიება"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"პრინტერები ვერ მოიძებნა"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"იბეჭდება <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"მიმდინარეობს <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>-ის გაუქმება"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"ბეჭდვის შეცდომა <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"პრინტერმა დაბლოკა <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"ბეჭდვის <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> დავალება"</item>
+    <item quantity="other" msgid="8746611264734222865">"ბეჭდვის <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> დავალება"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"გაუქმება"</string>
     <string name="restart" msgid="2472034227037808749">"გადატვირთვა"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"პრინტერთან კავშირი არ არის"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"უცნობი"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – მიუწვდომელია"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"ბეჭდვის დავალების გენერაცია ვერ ხერხდება"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"შავ-თეთრი"</item>
     <item msgid="2762241247228983754">"ფერი"</item>
diff --git a/packages/PrintSpooler/res/values-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index 9125e282a..74a15fd 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"ពណ៌"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ទិស"</string>
     <string name="label_pages" msgid="6300874667546617333">"ទំព័រ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ឧ. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ឧ. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"មើល​មុន​បោះពុម្ព"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"ដំឡើង​កម្មវិធី​មើល PDF សម្រាប់​ការ​មើល​ជា​មុន"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"កម្មវិធី​បោះពុម្ព​គាំង"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"​បង្កើត​ការ​ងារ​បោះពុម្ព"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"រក្សា​ទុក​ជា PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"ម៉ាស៊ីន​បោះពុម្ព​ទាំងអស់ ..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"ប្រអប់​បោះពុម្ព"</string>
     <string name="search" msgid="5421724265322228497">"ស្វែងរក"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ម៉ាស៊ីន​បោះពុម្ព​ទាំងអស់"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"បន្ថែម​សេវាកម្ម"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"បាន​បង្ហាញ​ប្រ​អប់​ស្វែងរក"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"បាន​លាក់​ប្រអប់​ស្វែងរក"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"បន្ថែម​ម៉ាស៊ីន​បោះពុម្ព"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"រក​ឃើញ​ម៉ាស៊ីន​បោះពុម្ព <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+    <item quantity="other" msgid="6533817036607128241">"រក​ឃើញ​ម៉ាស៊ីន​បោះពុម្ព <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ជ្រើស​សេវា​បោះពុម្ព"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ស្វែងរក​ម៉ាស៊ីន​បោះពុម្ព"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"រក​មិន​ឃើញ​ម៉ាស៊ីន​បោះពុម្ព"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"កំពុង​​បោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"ការ​បោះបង់ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"កំហុស​ម៉ាស៊ីន​បោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"ម៉ាស៊ីន​បោះពុម្ព​បាន​ទប់ស្កាត់ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"ការងារ​បោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"ការងារ​បោះពុម្ព <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"បោះបង់"</string>
     <string name="restart" msgid="2472034227037808749">"ចាប់ផ្ដើម​ឡើងវិញ"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មាន​​​ការ​ភ្ជាប់​ទៅ​ម៉ាស៊ីន​បោះពុម្ព"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"មិន​ស្គាល់"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – មិន​អាច​ប្រើ​បាន"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"មិន​អាច​បង្កើត​ការ​ងារ​បោះពុម្ព"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ស &amp; ខ្មៅ"</item>
     <item msgid="2762241247228983754">"ពណ៌"</item>
diff --git a/packages/PrintSpooler/res/values-ko/strings.xml b/packages/PrintSpooler/res/values-ko/strings.xml
index 0aef672..2f26099 100644
--- a/packages/PrintSpooler/res/values-ko/strings.xml
+++ b/packages/PrintSpooler/res/values-ko/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"색상"</string>
     <string name="label_orientation" msgid="2853142581990496477">"방향"</string>
     <string name="label_pages" msgid="6300874667546617333">"페이지 수(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"예: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"예: 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"인쇄 미리보기"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"미리보기용 PDF 뷰어 설치"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"인쇄 앱에 오류 발생"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"인쇄 작업 생성 중"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF로 저장"</string>
     <string name="all_printers" msgid="5018829726861876202">"모든 프린터…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"인쇄 대화상자"</string>
     <string name="search" msgid="5421724265322228497">"검색"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"모든 프린터"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"서비스 추가"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"검색창 표시됨"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"검색창 숨겨짐"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"프린터 추가"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"프린터 <xliff:g id="COUNT">%1$s</xliff:g>대 검색됨"</item>
+    <item quantity="other" msgid="6533817036607128241">"프린터 <xliff:g id="COUNT">%1$s</xliff:g>대 검색됨"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"인쇄 서비스 선택"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"프린터 검색 중"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"프린터 없음"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 인쇄 중"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> 취소 중"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"프린터 오류: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"차단된 프린터: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 인쇄 작업"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 인쇄 작업"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"취소"</string>
     <string name="restart" msgid="2472034227037808749">"다시 시작"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"프린터와 연결되지 않음"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"알 수 없음"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 사용할 수 없음"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"인쇄 작업을 생성할 수 없습니다."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"흑백"</item>
     <item msgid="2762241247228983754">"컬러"</item>
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
new file mode 100644
index 0000000..d68b77e
--- /dev/null
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+
+    <dimen name="printer_list_view_padding_start">48dip</dimen>
+    <dimen name="printer_list_view_padding_end">48dip</dimen>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values-lo-rLA/strings.xml b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
index cbbe9b6..8c0b37a 100644
--- a/packages/PrintSpooler/res/values-lo-rLA/strings.xml
+++ b/packages/PrintSpooler/res/values-lo-rLA/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"ສີ"</string>
     <string name="label_orientation" msgid="2853142581990496477">"ລວງ"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) ໜ້າ"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ຕົວຢ່າງ. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ຕົວຢ່າງ: 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"ເບິ່ງກ່ອນພິມ"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"ຕິດຕັ້ງໂປຼແກຼມເບິ່ງ PDF ເພື່ອເບິ່ງຕົວຢ່າງ"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"ແອັບຯພິມລົ້ມເຫລວ"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"ກຳລັງສ້າງວຽກພິມ"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"ບັນທຶກເປັ​​ນ PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"ທຸກເຄື່ອງພິມ..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"ໜ້າຕ່າງການພິມ"</string>
     <string name="search" msgid="5421724265322228497">"ຊອກຫາ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ທຸກເຄື່ອງພິມ"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ເພີ່ມບໍລິການ"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ກ່ອງຊອກຫາຖືກສະແດງ"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ກ່ອງຊອກຫາຖືກເຊື່ອງ"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"ເພີ່ມເຄື່ອງພິມ"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"ພົບ <xliff:g id="COUNT">%1$s</xliff:g> ເຄື່ອງພິມ"</item>
+    <item quantity="other" msgid="6533817036607128241">"ພົບ <xliff:g id="COUNT">%1$s</xliff:g> ເຄື່ອງພິມ"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"ເລືອກບໍລິການການພິມ"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"ກຳລັງຊອກຫາເຄື່ອງພິມ"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"ບໍ່ພົບເຄື່ອງພິມ"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"ກຳລັງພິມ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"ກຳລັງຍົກເລີກ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"ເຄື່ອງພິມເກີດຂໍ້ຜິດພາດ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"ເຄື່ອງພິມຖືກບລອກ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"ງານພິມ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"ງານພິມ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"ຍົກເລີກ"</string>
     <string name="restart" msgid="2472034227037808749">"ປິດເປີດໃໝ່"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ບໍ່ມີການເຊື່ອມຕໍ່ຫາເຄື່ອງພິມ"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - ບໍ່ມີຢູ່"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"ບໍ່​ສາ​ມາດ​ສ້າງວຽກພິມໄດ້"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ຂາວດຳ"</item>
     <item msgid="2762241247228983754">"ສີ"</item>
diff --git a/packages/PrintSpooler/res/values-lt/strings.xml b/packages/PrintSpooler/res/values-lt/strings.xml
index 624854f..708b528 100644
--- a/packages/PrintSpooler/res/values-lt/strings.xml
+++ b/packages/PrintSpooler/res/values-lt/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Spalva"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacija"</string>
     <string name="label_pages" msgid="6300874667546617333">"Puslapiai (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"pvz., 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"pvz., 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Spaudinio peržiūra"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Įdiegti PDF peržiūros priemonę"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Spausdinimo programa užstrigo"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Puslapiai"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generuojama spausd. užduotis"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Išsaugoti kaip PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Visi spausdintuvai…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Spausdinimo dialogo langas"</string>
     <string name="search" msgid="5421724265322228497">"Ieškoti"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Visi spausdintuvai"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Pridėti paslaugą"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Paieškos laukelis rodomas"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Paieškos laukelis paslėptas"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Pridėti spausdintuvą"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Rasta spausdintuvų: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+    <item quantity="other" msgid="6533817036607128241">"Rasta spausdintuvų: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pasirinkite spausdinimo paslaugą"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Ieškoma spausdintuvų"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nerasta spausdintuvų"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Spausdinama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Atšaukiama: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Spausdintuvo klaida: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Spausdintuvas užblokavo: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Spausdinimo užduotis: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Spausdinimo užduotys: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Atšaukti"</string>
     <string name="restart" msgid="2472034227037808749">"Paleisti iš naujo"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nėra ryšio su spausdintuvu"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nežinoma"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"„<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>“ – nepasiekiama"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nepavyko sukurti spausdinimo užduoties"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nespalvotas"</item>
     <item msgid="2762241247228983754">"Spalva"</item>
diff --git a/packages/PrintSpooler/res/values-lv/strings.xml b/packages/PrintSpooler/res/values-lv/strings.xml
index a7066fc..62af20b 100644
--- a/packages/PrintSpooler/res/values-lv/strings.xml
+++ b/packages/PrintSpooler/res/values-lv/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Krāsa"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Virziens"</string>
     <string name="label_pages" msgid="6300874667546617333">"Lapas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"piem., 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"piem., 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Drukas priekšskatījums"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalēt PDF skatītāju priekšskatīšanai"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Drukas lietotne avarēja"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Lapas"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Ģenerē drukas darbu…"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Saglabāt kā PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Visi printeri…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Drukāšanas dialoglodziņš"</string>
     <string name="search" msgid="5421724265322228497">"Meklēt"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Visi printeri"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Pievienot pakalpojumu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Meklēšanas lodziņš ir redzams."</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Meklēšanas lodziņš ir paslēpts."</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Pievienot printeri"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Atrasts <xliff:g id="COUNT">%1$s</xliff:g> printeris"</item>
+    <item quantity="other" msgid="6533817036607128241">"Atrasti <xliff:g id="COUNT">%1$s</xliff:g> printeri"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Izvēlieties drukāšanas pakalpojumu"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Printeru meklēšana"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Netika atrasts neviens printeris."</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Notiek darba <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> drukāšana…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Pārtrauc drukas darbu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printera kļūda ar darbu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printeris bloķēja darbu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Drukas darbs <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Drukas darbi <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Atcelt"</string>
     <string name="restart" msgid="2472034227037808749">"Restartēt"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nav savienojuma ar printeri"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"nezināms"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> — nav pieejams"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nevar ģenerēt drukas darbu"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Melnbalts"</item>
     <item msgid="2762241247228983754">"Krāsa"</item>
diff --git a/packages/PrintSpooler/res/values-mn-rMN/strings.xml b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
index 58dea51..0a64954 100644
--- a/packages/PrintSpooler/res/values-mn-rMN/strings.xml
+++ b/packages/PrintSpooler/res/values-mn-rMN/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Өнгө"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Чиглэл"</string>
     <string name="label_pages" msgid="6300874667546617333">"(<xliff:g id="PAGE_COUNT">%1$s</xliff:g>) хуудас"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"жнь. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ж.нь. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Хэвлэхээр урьдчилан харах"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Урьдчилан харахын тулд PDF харагчийг суулгах"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Хэвлэгч апп гацсан"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Хэвлэх ажил үүсгэж байна"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF болгож хадгалах"</string>
     <string name="all_printers" msgid="5018829726861876202">"Бүх принтерүүд…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Хэвлэх диалоги"</string>
     <string name="search" msgid="5421724265322228497">"Хайх"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Бүх принтерүүд"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Үйлчилгээ нэмэх"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Хайлтын нүдийг гаргах"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Хайлтын нүдийг далдлах"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Принтер нэмэх"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> принтер олдсон"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> принтер олдсон"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Хэвлэх үйлчилгээг сонгох"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Принтер хайж байна"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Принтер олдсонгүй"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Хэвлэж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Цуцлаж байна <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Принтерийн алдаа <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Принтер хориглогдсон <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> хэвлэх ажил"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> хэвлэх ажлууд"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Цуцлах"</string>
     <string name="restart" msgid="2472034227037808749">"Дахин эхлүүлэх"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Принтер холбогдоогүй байна"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"тодорхойгүй"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ашиглах боломжгүй"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Хэвлэх ажлыг үүсгэж чадсангүй"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Хар &amp; Цагаан"</item>
     <item msgid="2762241247228983754">"Өнгө"</item>
diff --git a/packages/PrintSpooler/res/values-ms-rMY/strings.xml b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
index 90fa63a..8bf0083 100644
--- a/packages/PrintSpooler/res/values-ms-rMY/strings.xml
+++ b/packages/PrintSpooler/res/values-ms-rMY/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Warna"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientasi"</string>
     <string name="label_pages" msgid="6300874667546617333">"Halaman (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"cth. 1–5, 8"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"cth. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Pratonton cetak"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Pasang pemapar PDF untuk pratonton"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Apl percetakan ranap"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Menjana kerja cetak"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Simpan sebagai PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Semua pencetak..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dialog cetakan"</string>
     <string name="search" msgid="5421724265322228497">"Cari"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Semua pencetak"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Tambahkan perkhidmatan"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Kotak carian ditunjukkan"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Kotak carian tersembunyi"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Tambah pencetak"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> pencetak ditemui"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> pencetak ditemui"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pilih perkhidmatan cetak"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Mencari pencetak"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Tiada pencetak ditemui"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Ralat pencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Pencetak disekat <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Kerja cetakan <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Kerja cetakan <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Batal"</string>
     <string name="restart" msgid="2472034227037808749">"Mulakan semula"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Tiada sambungan ke pencetak"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"tidak diketahui"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – tidak tersedia"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tidak dapat menjana kerja cetakan"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Hitam &amp; Putih"</item>
     <item msgid="2762241247228983754">"Warna"</item>
diff --git a/packages/PrintSpooler/res/values-nb/strings.xml b/packages/PrintSpooler/res/values-nb/strings.xml
index 44e17acb..dd660d0 100644
--- a/packages/PrintSpooler/res/values-nb/strings.xml
+++ b/packages/PrintSpooler/res/values-nb/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Farge"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Retning"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sider (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"f.eks. 2–5, 8"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"f.eks. 1–5, 8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Utskriftsforhåndsvisning"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installer PDF-leser for forhåndsvisning"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Utskriftsappen krasjet"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Sider"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Genererer utskriftsjobb"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Lagre som PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alle skrivere"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Skriv ut dialog"</string>
     <string name="search" msgid="5421724265322228497">"Søk"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alle skrivere"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Legg til tjeneste"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Søkefeltet vises"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Søkefeltet er skjult"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Legg til skriver"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> skriver ble funnet"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> skrivere ble funnet"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Velg utskriftstjeneste"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Søker etter skrivere"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Fant ingen skrivere"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Skriverfeil <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Skriveren blokkerte <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Utskriftsjobb for <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Utskriftsjobber for <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Avbryt"</string>
     <string name="restart" msgid="2472034227037808749">"Start på nytt"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen forbindelse med skriveren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ukjent"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – utilgjengelig"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kunne ikke generere utskriftsjobben"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svart og hvitt"</item>
     <item msgid="2762241247228983754">"Farge"</item>
diff --git a/packages/PrintSpooler/res/values-nl/strings.xml b/packages/PrintSpooler/res/values-nl/strings.xml
index 9c074df..ce4b0df 100644
--- a/packages/PrintSpooler/res/values-nl/strings.xml
+++ b/packages/PrintSpooler/res/values-nl/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Kleur"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Stand"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagina\'s (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"bijv. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"bijv. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Afdrukvoorbeeld"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Pdf-viewer installeren voor voorbeeld"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Afdruk-app gecrasht"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Pagina\'s"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Afdruktaak genereren"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Opslaan als pdf"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alle printers…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Afdrukdialoogvenster"</string>
     <string name="search" msgid="5421724265322228497">"Zoeken"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alle printers"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Service toevoegen"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Zoekvak weergegeven"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Zoekvak verborgen"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Printer toevoegen"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> printer gevonden"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> printers gevonden"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Afdrukservice kiezen"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Printers zoeken"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Geen printers gevonden"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> afdrukken"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> annuleren"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Printerfout <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> geblokkeerd door printer"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> afdruktaak"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> afdruktaken"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Annuleren"</string>
     <string name="restart" msgid="2472034227037808749">"Opnieuw starten"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Geen verbinding met printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"onbekend"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niet beschikbaar"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Kan de afdruktaak niet genereren"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Zwart-wit"</item>
     <item msgid="2762241247228983754">"Kleur"</item>
diff --git a/packages/PrintSpooler/res/values-pl/strings.xml b/packages/PrintSpooler/res/values-pl/strings.xml
index bcd57b2..5883863 100644
--- a/packages/PrintSpooler/res/values-pl/strings.xml
+++ b/packages/PrintSpooler/res/values-pl/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Kolor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientacja"</string>
     <string name="label_pages" msgid="6300874667546617333">"Strony (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"np. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"np. 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Podgląd wydruku"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Zainstaluj przeglądarkę PDF, by zobaczyć podgląd"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikacja drukująca uległa awarii"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Strony"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generowanie zadania wydruku"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Zapisz jako PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Wszystkie drukarki…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Okno drukowania"</string>
     <string name="search" msgid="5421724265322228497">"Szukaj"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Wszystkie drukarki"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Dodaj usługę"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Pole wyszukiwania jest widoczne"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Pole wyszukiwania jest ukryte"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Dodaj drukarkę"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Znaleziono <xliff:g id="COUNT">%1$s</xliff:g> drukarkę"</item>
+    <item quantity="other" msgid="6533817036607128241">"Znalezione drukarki: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Wybierz usługę drukowania"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Szukanie drukarek"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nie znaleziono drukarek"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Drukowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Anulowanie: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Błąd drukarki: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Drukarka zablokowała <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> zadanie drukowania"</item>
+    <item quantity="other" msgid="8746611264734222865">"Zadania drukowania: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Anuluj"</string>
     <string name="restart" msgid="2472034227037808749">"Od nowa"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Brak połączenia z drukarką"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"brak informacji"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – niedostępne"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nie udało się wygenerować zadania drukowania"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Czarno-białe"</item>
     <item msgid="2762241247228983754">"Kolor"</item>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index bc7a677..577d316 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Cor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"por exemplo, 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"p. ex. 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Pré-visualização de impressão"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalar o leitor de PDF para pré-visualização"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"A aplicação de impressão bloqueou"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Páginas"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"A gerar tarefa de impressão"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Guardar como PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Todas as impressoras..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Caixa de diálogo de impressão"</string>
     <string name="search" msgid="5421724265322228497">"Pesquisar"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Todas as impressoras"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Adicionar serviço"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Caixa de pesquisa apresentada"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Caixa de pesquisa ocultada"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Adicionar impressora"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> impressora encontrada"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> impressoras encontradas"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Escolher o serviço de impressão"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"A procurar impressoras"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"A imprimir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"A cancelar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Erro da impressora <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"A impressora bloqueou <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tarefa de impressão: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tarefas de impressão: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancelar"</string>
     <string name="restart" msgid="2472034227037808749">"Reiniciar"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem ligação à impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – indisponível"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar a tarefa de impressão"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index 6c63502..8d86086 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Cor"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientação"</string>
     <string name="label_pages" msgid="6300874667546617333">"Páginas (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ex.: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"Ex.: 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Visualização de impressão"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalar o visualizador de PDF"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"O aplicativo de impressão falhou"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Gerando trabalho de impressão"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Salvar como PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Todas as impressoras…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Diálogo de impressão"</string>
     <string name="search" msgid="5421724265322228497">"Pesquisar"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Todas as impressoras"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Adicionar serviço"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Caixa de pesquisa exibida"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Caixa de pesquisa oculta"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Adicionar impressora"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> impressora encontrada"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> impressoras encontradas"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Selecione o serviço de impressão"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Procurando impressoras"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Nenhuma impressora encontrada"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Cancelando <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Erro ao imprimir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"A impressora bloqueou <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Trabalho de impressão <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Trabalhos de impressão <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Cancelar"</string>
     <string name="restart" msgid="2472034227037808749">"Reiniciar"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Sem conexão com a impressora"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"desconhecido"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – não disponível"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Não foi possível gerar o trabalho de impressão"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Preto e branco"</item>
     <item msgid="2762241247228983754">"Cor"</item>
diff --git a/packages/PrintSpooler/res/values-ro/strings.xml b/packages/PrintSpooler/res/values-ro/strings.xml
index d194d65..82a8c8c 100644
--- a/packages/PrintSpooler/res/values-ro/strings.xml
+++ b/packages/PrintSpooler/res/values-ro/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Color"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientare"</string>
     <string name="label_pages" msgid="6300874667546617333">"Pagini (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ex. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"de ex. 1-5, 8, 11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Previzualizați printarea"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Instalați PDF viewer pentru previzualizare"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplicația de printare s-a blocat"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Pagini"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Se generează sarcină printare"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Salvați ca PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Toate imprimantele..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Caseta de dialog de printare"</string>
     <string name="search" msgid="5421724265322228497">"Căutați"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Toate imprimantele"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Adăugați un serviciu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Caseta de căutare este afișată"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Caseta de căutare este ascunsă"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Adăugați o imprimantă"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> imprimantă găsită"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> (de) imprimante găsite"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Alegeți serviciul de printare"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Se caută imprimante"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nu au fost găsite imprimante"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Se printează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Se anulează <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Eroare de printare: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printare blocată: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Sarcină de printare <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Sarcini de printare <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Anulați"</string>
     <string name="restart" msgid="2472034227037808749">"Reporniți"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Nu există conexiune la o imprimantă"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"necunoscut"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - indisponibil"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Nu s-a putut genera sarcina de printare"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Alb-negru"</item>
     <item msgid="2762241247228983754">"Color"</item>
diff --git a/packages/PrintSpooler/res/values-ru/strings.xml b/packages/PrintSpooler/res/values-ru/strings.xml
index bbf66fb..3d4cf6a 100644
--- a/packages/PrintSpooler/res/values-ru/strings.xml
+++ b/packages/PrintSpooler/res/values-ru/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Цветной"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Ориентация"</string>
     <string name="label_pages" msgid="6300874667546617333">"СТРАНИЦЫ (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"напр., 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"напр., 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Предварительный просмотр"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Установить средство просмотра PDF"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Сбой приложения печати"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Количество страниц"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Создание задания печати…"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Сохранить как PDF-файл"</string>
     <string name="all_printers" msgid="5018829726861876202">"Все принтеры"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Диалоговое окно печати"</string>
     <string name="search" msgid="5421724265322228497">"Поиск"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Все принтеры"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Добавить службу печати"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Окно поиска показано"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Окно поиска скрыто"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Добавить принтер"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Найден <xliff:g id="COUNT">%1$s</xliff:g> принтер"</item>
+    <item quantity="other" msgid="6533817036607128241">"Найдено принтеров: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Выберите службу печати"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Поиск принтеров…"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Ничего не найдено"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Печать задания \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\"…"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отмена задания <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>…"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Ошибка задания \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\""</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Задание \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" заблокировано"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Задание печати: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Задания печати: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Отмена"</string>
     <string name="restart" msgid="2472034227037808749">"Повторить"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нет связи с принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"неизвестно"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступен"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Не удалось отправить документ на печать."</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Черно-белое"</item>
     <item msgid="2762241247228983754">"Цветное"</item>
diff --git a/packages/PrintSpooler/res/values-sk/strings.xml b/packages/PrintSpooler/res/values-sk/strings.xml
index 81361ad..6135a19 100644
--- a/packages/PrintSpooler/res/values-sk/strings.xml
+++ b/packages/PrintSpooler/res/values-sk/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Farba"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientácia"</string>
     <string name="label_pages" msgid="6300874667546617333">"STRÁNKY (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"napr.: 1–5, 8"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"napr. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Ukážka pred tlačou"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Inštalovať zobrazovač PDF na zobrazenie ukážky"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikácia pre tlač zlyhala"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Strany"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Generuje sa tlačová úloha"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Uložiť ako PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Všetky tlačiarne..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dialógové okno tlače"</string>
     <string name="search" msgid="5421724265322228497">"VYHĽADÁVANIE"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Všetky tlačiarne"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Pridať službu"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Vyhľadávacie pole sa zobrazuje"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Vyhľadávacie pole je skryté"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Pridať tlačiareň"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Našla sa <xliff:g id="COUNT">%1$s</xliff:g> tlačiareň"</item>
+    <item quantity="other" msgid="6533817036607128241">"Počet nájdených tlačiarní: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Výber tlačovej služby"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Vyhľadávanie tlačiarní"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Nenašli sa žiadne tlačiarne"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Prebieha tlač úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Prebieha zrušenie úlohy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Chyba tlačiarne – úloha <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Tlačiareň zablok. úlohu <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Počet tlačových úloh: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Počet tlačových úloh: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Zrušiť"</string>
     <string name="restart" msgid="2472034227037808749">"Spustiť znova"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Žiadne pripojenie k tlačiarni"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznáme"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – nie je k dispozícii"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tlačovú úlohu nie je možné vytvoriť"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Čiernobiele"</item>
     <item msgid="2762241247228983754">"Farba"</item>
diff --git a/packages/PrintSpooler/res/values-sl/strings.xml b/packages/PrintSpooler/res/values-sl/strings.xml
index 0886831..b369cf3 100644
--- a/packages/PrintSpooler/res/values-sl/strings.xml
+++ b/packages/PrintSpooler/res/values-sl/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Barvno"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Postavitev"</string>
     <string name="label_pages" msgid="6300874667546617333">"Št. strani (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"npr. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"npr. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Predogled tiskanja"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Za predogled namestite pregledovalnik za PDF-je"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Aplikacija za tiskanje se je zrušila"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Ustvarjanje zahteve za tisk"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Shrani kot PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Vsi tiskalniki …"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Pogovorno okno za tiskanje"</string>
     <string name="search" msgid="5421724265322228497">"Iskanje"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Vsi tiskalniki"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Dodaj storitev"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Iskalno polje je prikazano"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Iskalno polje je skrito"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Dodajanje tiskalnika"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Najden <xliff:g id="COUNT">%1$s</xliff:g> tiskalnik"</item>
+    <item quantity="other" msgid="6533817036607128241">"Število najdenih tiskalnikov: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Izberite tiskalno storitev"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Iskanje tiskalnikov"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Tiskalnikov ni mogoče najti"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Tiskanje: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Preklic: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Napaka tiskalnika: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Tiskalnik je blokiral <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Tiskalno opravilo: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Tiskalna opravila: <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Prekliči"</string>
     <string name="restart" msgid="2472034227037808749">"Začni znova"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ni povezave s tiskalnikom"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"neznano"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ni na voljo"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Tiskalnega opravila ni bilo mogoče ustvariti"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Črno-belo"</item>
     <item msgid="2762241247228983754">"Barvno"</item>
diff --git a/packages/PrintSpooler/res/values-sr/strings.xml b/packages/PrintSpooler/res/values-sr/strings.xml
index 93ca41f..a09fa4c 100644
--- a/packages/PrintSpooler/res/values-sr/strings.xml
+++ b/packages/PrintSpooler/res/values-sr/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Боја"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Положај"</string>
     <string name="label_pages" msgid="6300874667546617333">"Странице (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"нпр. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"нпр. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Преглед пре штампања"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Инсталирај PDF приказивач за преглед"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Апликација за штампање је отказала"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Странице"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Генерисање задатка за штампање"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Сачувај као PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Сви штампачи…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Дијалог за штампање"</string>
     <string name="search" msgid="5421724265322228497">"Претражи"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Сви штампачи"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Додај услугу"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Оквир за претрагу је приказан"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Оквир за претрагу је сакривен"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Додај штампач"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Пронађен је <xliff:g id="COUNT">%1$s</xliff:g> штампач"</item>
+    <item quantity="other" msgid="6533817036607128241">"Пронађено је <xliff:g id="COUNT">%1$s</xliff:g> штампача"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Изаберите услугу штампања"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Претрага штампача"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Није пронађен ниједан штампач"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Штампа се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Отказује се <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Грешка штампача <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Штампач је блокирао <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Задатак штампања <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Задаци штампања <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Откажи"</string>
     <string name="restart" msgid="2472034227037808749">"Поново покрени"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Нема везе са штампачем"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"непознато"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – недоступан"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Није могуће генерисати задатак за штампање"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Црно-бело"</item>
     <item msgid="2762241247228983754">"Боја"</item>
diff --git a/packages/PrintSpooler/res/values-sv/strings.xml b/packages/PrintSpooler/res/values-sv/strings.xml
index 000f88e..85d7e6e 100644
--- a/packages/PrintSpooler/res/values-sv/strings.xml
+++ b/packages/PrintSpooler/res/values-sv/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Färg"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Orientering"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sidor (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"t.ex. 1–5, 8"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"t.ex. 1–5,8,11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Förhandsgranskning"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Installera PDF-läsare för förhandsgranskning"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Utskriftsappen kraschade"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Sidor"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Genererar utskriftsjobb"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Spara som PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Alla skrivare ..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dialogrutan Skriv ut"</string>
     <string name="search" msgid="5421724265322228497">"Sök"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Alla skrivare"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Lägg till tjänst"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Sökrutan visas"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Sökrutan är dold"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Lägg till skrivare"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> skrivare hittades"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> skrivare hittades"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Välj utskriftstjänst"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Söker efter skrivare"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Det gick inte att hitta några skrivare"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Skriver ut <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Avbryter <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Skrivarfel för <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Skrivaren har blockerat <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Utskriftsjobb – <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Utskriftsjobb – <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Avbryt"</string>
     <string name="restart" msgid="2472034227037808749">"Starta om"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Ingen anslutning till skrivaren"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"okänt"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – inte tillgänglig"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Det gick inte att skapa utskriftsjobbet"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Svartvit"</item>
     <item msgid="2762241247228983754">"Färg"</item>
diff --git a/packages/PrintSpooler/res/values-sw/strings.xml b/packages/PrintSpooler/res/values-sw/strings.xml
index 7e655ee..b8842e7 100644
--- a/packages/PrintSpooler/res/values-sw/strings.xml
+++ b/packages/PrintSpooler/res/values-sw/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Rangi"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Mkao"</string>
     <string name="label_pages" msgid="6300874667546617333">"Kurasa (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"k.m. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"k.m. 1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Chungulia kwanza kabla ya kuchapisha"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Sakinisha kitazamaji cha PDF kwa onyesho la kuchungulia"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Programu ya kuchapisha imeacha kufanya kazi"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Kurasa"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Inazanzisha kazi ya kuchapisha"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Hifadhi kama PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Printa zote..."</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Chapisha mazungumzo"</string>
     <string name="search" msgid="5421724265322228497">"Tafuta"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Printa zote"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Ongeza huduma"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Kisanduku cha kutafutia kimeonyeshwa"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Kisanduku cha kutafutia kimefichwa"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Ongeza printa"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Printa <xliff:g id="COUNT">%1$s</xliff:g> imepatikana"</item>
+    <item quantity="other" msgid="6533817036607128241">"Printa <xliff:g id="COUNT">%1$s</xliff:g> zimepatikana"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Chagua huduma ya printa"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Inatafuta printa"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Hakuna printa zilizopatikana"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Inachapisha <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Inaghairi <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Hitilafu ya kuchapisha <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Printa imefungwa <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Kazi ya kuchapisha ya <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Kazi za kuchapisha za <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Ghairi"</string>
     <string name="restart" msgid="2472034227037808749">"Anzisha upya"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hakuna muunganisho kwa printa"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"haijulikani"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - haipatikani"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Haikuweza kuunda kazi ya kuchapisha"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Nyeusi na Nyeupe"</item>
     <item msgid="2762241247228983754">"Rangi"</item>
diff --git a/packages/PrintSpooler/res/values-th/strings.xml b/packages/PrintSpooler/res/values-th/strings.xml
index 9f6e226..08b2e78 100644
--- a/packages/PrintSpooler/res/values-th/strings.xml
+++ b/packages/PrintSpooler/res/values-th/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"สี"</string>
     <string name="label_orientation" msgid="2853142581990496477">"การวางแนว"</string>
     <string name="label_pages" msgid="6300874667546617333">"หน้า (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"เช่น 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"เช่น 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"ตัวอย่างก่อนพิมพ์"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"ติดตั้งโปรแกรมดู PDF เพื่อดูหน้าตัวอย่าง"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"แอปการพิมพ์ขัดข้อง"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"กำลังสร้างงานพิมพ์"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"บันทึกเป็น PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"เครื่องพิมพ์ทั้งหมด…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"ช่องโต้ตอบการพิมพ์"</string>
     <string name="search" msgid="5421724265322228497">"ค้นหา"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"เครื่องพิมพ์ทั้งหมด"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"เพิ่มบริการ"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"แสดงช่องค้นหาอยู่"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ซ่อนช่องค้นหาอยู่"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"เพิ่มเครื่องพิมพ์"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"พบเครื่องพิมพ์ <xliff:g id="COUNT">%1$s</xliff:g> เครื่อง"</item>
+    <item quantity="other" msgid="6533817036607128241">"พบเครื่องพิมพ์ <xliff:g id="COUNT">%1$s</xliff:g> เครื่อง"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"เลือกบริการพิมพ์"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"กำลังค้นหาเครื่องพิมพ์"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"ไม่พบเครื่องพิมพ์"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"กำลังพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"กำลังยกเลิก <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"ข้อผิดพลาดเครื่องพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"เครื่องพิมพ์ได้บล็อก <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"งานพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"งานพิมพ์ <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"ยกเลิก"</string>
     <string name="restart" msgid="2472034227037808749">"เริ่มต้นใหม่"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"ไม่มีการเชื่อมต่อไปยังเครื่องพิมพ์"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"ไม่ทราบ"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ไม่พร้อมใช้งาน"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"ไม่สามารถสร้างงานพิมพ์"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"ขาวดำ"</item>
     <item msgid="2762241247228983754">"สี"</item>
diff --git a/packages/PrintSpooler/res/values-tl/strings.xml b/packages/PrintSpooler/res/values-tl/strings.xml
index 2dc3c39..c2c9592 100644
--- a/packages/PrintSpooler/res/values-tl/strings.xml
+++ b/packages/PrintSpooler/res/values-tl/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Kulay"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Oryentasyon"</string>
     <string name="label_pages" msgid="6300874667546617333">"Mga Pahina (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"hal. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"hal. 1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Preview sa pag-print"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Mag-install ng PDF viewer para sa pag-preview"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Nag-crash ang app sa pag-print"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Gumagawa ng pag-print"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"I-save bilang PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Lahat ng printer…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Dialog ng pag-print"</string>
     <string name="search" msgid="5421724265322228497">"Hanapin"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Lahat ng printer"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Magdagdag ng serbisyo"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Ipinapakita ang box para sa paghahanap"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Nakatago ang box para sa paghahanap"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Magdagdag ng printer"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> printer ang nakita"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> (na) printer ang nakita"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Pumili ng serbisyo ng pag-print"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Naghahanap ng mga printer"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Walang mga printer na nakita"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Pini-print ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Kinakansela ang <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Error sa printer <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Naka-block ang Printer <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Pag-print ng <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Mga pag-print ng <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Kanselahin"</string>
     <string name="restart" msgid="2472034227037808749">"I-restart"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Hindi nakakonekta sa printer"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"hindi alam"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – hindi available"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Hindi mabuo ang pag-print"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Black &amp; White"</item>
     <item msgid="2762241247228983754">"Kulay"</item>
diff --git a/packages/PrintSpooler/res/values-tr/strings.xml b/packages/PrintSpooler/res/values-tr/strings.xml
index bfc80af..ab82666 100644
--- a/packages/PrintSpooler/res/values-tr/strings.xml
+++ b/packages/PrintSpooler/res/values-tr/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Renkli"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Sayfa yönü"</string>
     <string name="label_pages" msgid="6300874667546617333">"Sayfa (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ör. 1-5, 8, 11-13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"ör. 1-5,8,11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Yazdırmayı önizle"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Önizlemek için PDF görüntüleyici yükleyin"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Yazdırma uygulaması kilitlendi"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Sayfa"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Yazdırma işi oluşturuluyor"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"PDF olarak kaydet"</string>
     <string name="all_printers" msgid="5018829726861876202">"Tüm yazıcılar…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Yazdırma iletişim kutusu"</string>
     <string name="search" msgid="5421724265322228497">"Ara"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Tüm yazıcılar"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Hizmet ekle"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Arama kutusu gösteriliyor"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Arama kutusu gizli"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Yazıcı ekle"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> yazıcı bulundu"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> yazıcı bulundu"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Yazdırma hizmetini seçin"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Yazıcılar aranıyor"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Yazıcı bulunamadı"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> yazdırılıyor"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> iptal ediliyor"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Yazıcı hatası: <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Yazıcı <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> işini engelledi"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> yazdırma işi"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> yazdırma işleri"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"İptal"</string>
     <string name="restart" msgid="2472034227037808749">"Yeniden başlat"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Yazıcı bağlantısı yok"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"bilinmiyor"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – kullanılamıyor"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Yazdırma işi oluşturulamadı"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Siyah Beyaz"</item>
     <item msgid="2762241247228983754">"Renkli"</item>
diff --git a/packages/PrintSpooler/res/values-uk/strings.xml b/packages/PrintSpooler/res/values-uk/strings.xml
index bd8c375..21cf0e9 100644
--- a/packages/PrintSpooler/res/values-uk/strings.xml
+++ b/packages/PrintSpooler/res/values-uk/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Колір"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Орієнтація"</string>
     <string name="label_pages" msgid="6300874667546617333">"Сторінки (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"напр.,1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"напр.,1–5, 8, 11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Версія для друку"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Установити засіб перегляду PDF"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Програма друку аварійно завершила роботу"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Створюється завдання друку"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Зберегти як PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Усі принтери…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Діалогове вікно друку"</string>
     <string name="search" msgid="5421724265322228497">"Пошук"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Усі принтери"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Додати службу"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Вікно пошуку показано"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Вікно пошуку сховано"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Додати принтер"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Знайдено принтерів: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+    <item quantity="other" msgid="6533817036607128241">"Знайдено принтерів: <xliff:g id="COUNT">%1$s</xliff:g>"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Вибрати службу друку"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Пошук принтерів"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Принтери не знайдено"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" друкується"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" скасовується"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Помилка завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\""</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" заблоковано"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Завдання друку <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Завдання друку <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Скасувати"</string>
     <string name="restart" msgid="2472034227037808749">"Перезапустити"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Немає з’єднання з принтером"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"невідомо"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"Завдання \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" не доступне"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Не вдалося створити завдання друку"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Чорно-білий"</item>
     <item msgid="2762241247228983754">"Колір"</item>
diff --git a/packages/PrintSpooler/res/values-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index 6546f0e..9582f00 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/strings.xml
@@ -25,7 +25,7 @@
     <string name="label_color" msgid="1108690305218188969">"Màu"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Hướng"</string>
     <string name="label_pages" msgid="6300874667546617333">"Trang (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"ví dụ: 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"Ví dụ: 1—5, 8, 11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Xem trước bản in"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Cài đặt trình xem PDF để xem trước"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Ứng dụng in gặp lỗi"</string>
@@ -33,32 +33,34 @@
     <string name="generating_print_job" msgid="3119608742651698916">"Đang tạo lệnh in"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Lưu dưới dạng PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Tất cả máy in…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Hộp thoại in"</string>
     <string name="search" msgid="5421724265322228497">"Tìm kiếm"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Tất cả máy in"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Thêm dịch vụ"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Hiển thị hộp tìm kiếm"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Ẩn hộp tìm kiếm"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Thêm máy in"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"Đã tìm thấy <xliff:g id="COUNT">%1$s</xliff:g> máy in"</item>
+    <item quantity="other" msgid="6533817036607128241">"Đã tìm thấy <xliff:g id="COUNT">%1$s</xliff:g> máy in"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Chọn dịch vụ in"</string>
     <string name="print_searching_for_printers" msgid="6550424555079932867">"Đang tìm kiếm máy in"</string>
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_no_printers" msgid="4869403323900054866">"Không tìm thấy máy in"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"In <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Hủy <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Lỗi máy in <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Máy in đã chặn <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"Lệnh in <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+    <item quantity="other" msgid="8746611264734222865">"Lệnh in <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Hủy"</string>
     <string name="restart" msgid="2472034227037808749">"Bắt đầu lại"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Không có kết nối nào với máy in"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"không xác định"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – không khả dụng"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Không thể tạo lệnh in"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Đen trắng"</item>
     <item msgid="2762241247228983754">"Màu"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
index 0e9b457..606baea 100644
--- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"颜色"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"页数 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"例如:1–5、8、11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"例如:1-5、8、11-13"</string>
     <string name="print_preview" msgid="8010217796057763343">"打印预览"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"安装 PDF 查看器以便预览"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"打印应用崩溃了"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"页数"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"正在生成打印作业"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"保存为 PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"所有打印机…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"打印对话框"</string>
     <string name="search" msgid="5421724265322228497">"搜索"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"所有打印机"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"添加服务"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"搜索框已显示"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"搜索框已隐藏"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"添加打印机"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"找到<xliff:g id="COUNT">%1$s</xliff:g>台打印机"</item>
+    <item quantity="other" msgid="6533817036607128241">"找到<xliff:g id="COUNT">%1$s</xliff:g>台打印机"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"选择打印服务"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜索打印机"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"找不到打印机"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"打印机在打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”时出错"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"打印机拒绝打印“<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>”"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"“<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>”打印作业"</item>
+    <item quantity="other" msgid="8746611264734222865">"“<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g>”打印作业"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"取消"</string>
     <string name="restart" msgid="2472034227037808749">"重新开始"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"未与打印机建立连接"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"未知"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> - 无法使用"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"无法生成打印作业"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rHK/strings.xml b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
index 71215c8..2f16a22 100644
--- a/packages/PrintSpooler/res/values-zh-rHK/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rHK/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"顏色"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"頁數 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"例如:1–5、8、11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"例如:1–5、8、11–13"</string>
     <string name="print_preview" msgid="8010217796057763343">"預覽列印"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"安裝預覽所需的 PDF 檢視器"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"列印應用程式當機了"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"頁數"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"正在產生列印工作"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"儲存為 PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"所有打印機…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"列印對話方塊"</string>
     <string name="search" msgid="5421724265322228497">"搜尋"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"所有打印機"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"新增服務"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"搜尋框已顯示"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"搜尋框已隱藏"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"新增打印機"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"找到 <xliff:g id="COUNT">%1$s</xliff:g> 部打印機"</item>
+    <item quantity="other" msgid="6533817036607128241">"找到 <xliff:g id="COUNT">%1$s</xliff:g> 部打印機"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋打印機"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"找不到打印機"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"打印機錯誤:<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"打印機已封鎖 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"一項 <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 列印工作"</item>
+    <item quantity="other" msgid="8746611264734222865">"多項 <xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 列印工作"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"取消"</string>
     <string name="restart" msgid="2472034227037808749">"重新開始"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與打印機連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zh-rTW/strings.xml b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
index 66ceb41..18908118 100644
--- a/packages/PrintSpooler/res/values-zh-rTW/strings.xml
+++ b/packages/PrintSpooler/res/values-zh-rTW/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"色彩"</string>
     <string name="label_orientation" msgid="2853142581990496477">"方向"</string>
     <string name="label_pages" msgid="6300874667546617333">"頁數 (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"例如:1–5、8、11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"例如:1—5,8,11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"列印預覽"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"安裝預覽所需的 PDF 檢視器"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"列印應用程式當機了"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"頁數"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"正在產生列印工作"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"儲存為 PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"所有印表機…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"印表機對話方塊"</string>
     <string name="search" msgid="5421724265322228497">"搜尋"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"所有印表機"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"新增服務"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"搜尋框已顯示"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"搜尋框已隱藏"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"新增印表機"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"找到 <xliff:g id="COUNT">%1$s</xliff:g> 台印表機"</item>
+    <item quantity="other" msgid="6533817036607128241">"找到 <xliff:g id="COUNT">%1$s</xliff:g> 台印表機"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"選擇列印服務"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜尋印表機"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"找不到印表機"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"正在列印 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"正在取消 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"印表機發生錯誤:<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"印表機封鎖了 <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 個列印工作"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> 個列印工作"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"取消"</string>
     <string name="restart" msgid="2472034227037808749">"重新開始"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"尚未與印表機建立連線"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"不明"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – 無法使用"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"無法產生列印工作"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"黑白"</item>
     <item msgid="2762241247228983754">"彩色"</item>
diff --git a/packages/PrintSpooler/res/values-zu/strings.xml b/packages/PrintSpooler/res/values-zu/strings.xml
index e1cb758..06472e4 100644
--- a/packages/PrintSpooler/res/values-zu/strings.xml
+++ b/packages/PrintSpooler/res/values-zu/strings.xml
@@ -25,42 +25,42 @@
     <string name="label_color" msgid="1108690305218188969">"Umbala"</string>
     <string name="label_orientation" msgid="2853142581990496477">"Umumo"</string>
     <string name="label_pages" msgid="6300874667546617333">"Amakhasi (<xliff:g id="PAGE_COUNT">%1$s</xliff:g>)"</string>
-    <string name="pages_range_example" msgid="4069269138547562081">"isb. 1–5, 8, 11–13"</string>
+    <string name="pages_range_example" msgid="8558694453556945172">"isb. 1—5, 8, 11—13"</string>
     <string name="print_preview" msgid="8010217796057763343">"Ukubuka kuqala kokuphrinta"</string>
     <string name="install_for_print_preview" msgid="6366303997385509332">"Faka isibukeli se-PDF ukuze uhlole kuqala"</string>
     <string name="printing_app_crashed" msgid="854477616686566398">"Ukuphrinta uhlelo lokusebenza kukhubazekile"</string>
-    <!-- no translation found for page_count_unknown (6058852665954511124) -->
-    <skip />
+    <string name="page_count_unknown" msgid="6058852665954511124">"Amakhasi"</string>
     <string name="generating_print_job" msgid="3119608742651698916">"Ikhiqiza umsebenzi wokuphrinta"</string>
     <string name="save_as_pdf" msgid="5718454119847596853">"Londoloza njenge-PDF"</string>
     <string name="all_printers" msgid="5018829726861876202">"Wonke amaphrinta…"</string>
-    <!-- no translation found for print_dialog (32628687461331979) -->
-    <skip />
+    <string name="print_dialog" msgid="32628687461331979">"Ingxoxo yokuphrinta"</string>
     <string name="search" msgid="5421724265322228497">"Sesha"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"Wonke amaphrinta"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"Engeza isevisi"</string>
-    <!-- no translation found for print_search_box_shown_utterance (7967404953901376090) -->
-    <skip />
-    <!-- no translation found for print_search_box_hidden_utterance (5727755169343113351) -->
-    <skip />
-    <!-- no translation found for print_add_printer (1088656468360653455) -->
-    <skip />
-    <!-- no translation found for print_search_result_count_utterance:one (4484953260685964252) -->
-    <!-- no translation found for print_search_result_count_utterance:other (6533817036607128241) -->
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Ibhokisi lokuhlola libonisiwe"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Ibhokisi lokusesha lifihliwe"</string>
+    <string name="print_add_printer" msgid="1088656468360653455">"Engeza iphrinta"</string>
+  <plurals name="print_search_result_count_utterance">
+    <item quantity="one" msgid="4484953260685964252">"<xliff:g id="COUNT">%1$s</xliff:g> iphrinta itholiwe"</item>
+    <item quantity="other" msgid="6533817036607128241">"<xliff:g id="COUNT">%1$s</xliff:g> amaphrinta atholiwe"</item>
+  </plurals>
     <string name="choose_print_service" msgid="3740309762324459694">"Khetha isevisi yephrinta"</string>
-    <!-- no translation found for print_searching_for_printers (6550424555079932867) -->
-    <skip />
-    <!-- no translation found for print_no_printers (4869403323900054866) -->
-    <skip />
+    <string name="print_searching_for_printers" msgid="6550424555079932867">"Isesha amaphrinta"</string>
+    <string name="print_no_printers" msgid="4869403323900054866">"Awekho amaphrinta atholiwe"</string>
     <string name="printing_notification_title_template" msgid="295903957762447362">"Iphrinta i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"Ikhansela i-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="failed_notification_title_template" msgid="2256217208186530973">"Iphutha lephrinta ye-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="blocked_notification_title_template" msgid="1175435827331588646">"Iphrinta engu-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> ivinjelwe"</string>
+  <plurals name="composite_notification_title_template">
+    <item quantity="one" msgid="5866624638054847057">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> umsebenzi wokuphrinta"</item>
+    <item quantity="other" msgid="8746611264734222865">"<xliff:g id="PRINT_JOB_NAME">%1$d</xliff:g> imisebenzi yokuphrinta"</item>
+  </plurals>
     <string name="cancel" msgid="4373674107267141885">"Khansela"</string>
     <string name="restart" msgid="2472034227037808749">"Qala kabusha"</string>
     <string name="no_connection_to_printer" msgid="2159246915977282728">"Akukho ukuxhumana kuphrinta"</string>
     <string name="reason_unknown" msgid="5507940196503246139">"akwaziwa"</string>
     <string name="printer_unavailable" msgid="2434170617003315690">"I-<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – ayitholakali"</string>
+    <string name="print_error_default_message" msgid="8568506918983980567">"Ayikwazanga ukukhiqiza umsebenzi wokuphrinta"</string>
   <string-array name="color_mode_labels">
     <item msgid="7602948745415174937">"Okumnyama nokumhlophe"</item>
     <item msgid="2762241247228983754">"Umbala"</item>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index e5a9d5d..e9c925c 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -26,4 +26,7 @@
 
     <dimen name="print_dialog_frame_max_width_dip">400dip</dimen>
 
-</resources>
\ No newline at end of file
+    <dimen name="printer_list_view_padding_start">16dip</dimen>
+    <dimen name="printer_list_view_padding_end">16dip</dimen>
+
+</resources>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index c82a20e..d74b414 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -44,7 +44,7 @@
     <string name="label_pages">Pages (<xliff:g id="page_count" example="5">%1$s</xliff:g>)</string>
 
     <!-- Page range exmple used as a hint of how to specify such. [CHAR LIMIT=20] -->
-    <string name="pages_range_example">e.g. 1&#8211;5, 8, 11&#8211;13</string>
+    <string name="pages_range_example">e.g. 1&#8212;5,8,11&#8212;13</string>
 
     <!-- Title for the pring preview button .[CHAR LIMIT=30] -->
     <string name="print_preview">Print preview</string>
@@ -121,6 +121,12 @@
     <!-- Template for the notificaiton label for a blocked print job. [CHAR LIMIT=25] -->
     <string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
 
+    <!-- Template for the notificaiton label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
+    <plurals name="composite_notification_title_template">
+        <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item>
+        <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item>
+    </plurals>
+
     <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
     <string name="cancel">Cancel</string>
 
@@ -136,6 +142,9 @@
     <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
     <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> &#8211; unavailable</string>
 
+    <!-- Default message of an alert dialog for app error while generating a print job. [CHAR LIMIT=50] -->
+    <string name="print_error_default_message">Couldn\'t generate print job</string>
+
     <!-- Arrays -->
 
     <!-- Color mode labels. -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
index 8aa290c..0601467 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -254,6 +254,9 @@
         if (isStarted() && mDiscoverySession != null
                 && mDiscoverySession.isPrinterDiscoveryStarted()) {
             if (mTrackedPrinter != null) {
+                if (mTrackedPrinter.equals(printerId)) {
+                    return;
+                }
                 mDiscoverySession.stopPrinterStateTracking(mTrackedPrinter);
             }
             mTrackedPrinter = printerId;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
index 2bd0443..968a8bf 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/NotificationController.java
@@ -17,11 +17,14 @@
 package com.android.printspooler;
 
 import android.app.Notification;
+import android.app.Notification.InboxStyle;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
@@ -35,6 +38,9 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class is responsible for updating the print notifications
  * based on print job state transitions.
@@ -60,29 +66,50 @@
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
-    public void onPrintJobStateChanged(PrintJobInfo printJob) {
-        if (DEBUG) {
-            Log.i(LOG_TAG, "onPrintJobStateChanged() printJobId: "
-                    + printJob.getId().flattenToString() + " state:"
-                    + PrintJobInfo.stateToString(printJob.getState()));
-        }
-        switch (printJob.getState()) {
-            case PrintJobInfo.STATE_QUEUED:
-            case PrintJobInfo.STATE_STARTED: {
-                createPrintingNotification(printJob);
-            } break;
+    public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
+        List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
 
+        final int printJobCount = printJobs.size();
+        for (int i = 0; i < printJobCount; i++) {
+            PrintJobInfo printJob = printJobs.get(i);
+            if (shouldNotifyForState(printJob.getState())) {
+                notifyPrintJobs.add(printJob);
+            }
+        }
+
+        updateNotification(notifyPrintJobs);
+    }
+
+    private void updateNotification(List<PrintJobInfo> printJobs) {
+        if (printJobs.size() <= 0) {
+            removeNotification();
+        } else if (printJobs.size() == 1) {
+            createSimpleNotification(printJobs.get(0));
+        } else {
+            createStackedNotification(printJobs);
+        }
+    }
+
+    private void createSimpleNotification(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
             case PrintJobInfo.STATE_FAILED: {
                 createFailedNotification(printJob);
             } break;
 
-            case PrintJobInfo.STATE_COMPLETED:
-            case PrintJobInfo.STATE_CANCELED: {
-                removeNotification(printJob.getId());
+            case PrintJobInfo.STATE_BLOCKED: {
+                if (!printJob.isCancelling()) {
+                    createBlockedNotification(printJob);
+                } else {
+                    createCancellingNotification(printJob);
+                }
             } break;
 
-            case PrintJobInfo.STATE_BLOCKED: {
-                createBlockedNotification(printJob);
+            default: {
+                if (!printJob.isCancelling()) {
+                    createPrintingNotification(printJob);
+                } else {
+                    createCancellingNotification(printJob);
+                }
             } break;
         }
     }
@@ -90,24 +117,22 @@
     private void createPrintingNotification(PrintJobInfo printJob) {
         Notification.Builder builder = new Notification.Builder(mContext)
                 .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(com.android.internal.R.drawable.ic_print)
-                .setContentTitle(mContext.getString(R.string.printing_notification_title_template,
-                        printJob.getLabel()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
                         createCancelIntent(printJob))
                 .setContentText(printJob.getPrinterName())
                 .setWhen(System.currentTimeMillis())
                 .setOngoing(true)
                 .setShowWhen(true);
-        mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+        mNotificationManager.notify(0, builder.build());
     }
 
     private void createFailedNotification(PrintJobInfo printJob) {
         Notification.Builder builder = new Notification.Builder(mContext)
                 .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
-                .setContentTitle(mContext.getString(R.string.failed_notification_title_template,
-                        printJob.getLabel()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
                         createCancelIntent(printJob))
                 .addAction(R.drawable.ic_restart, mContext.getString(R.string.restart),
@@ -116,32 +141,109 @@
                 .setWhen(System.currentTimeMillis())
                 .setOngoing(true)
                 .setShowWhen(true);
-        mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+        mNotificationManager.notify(0, builder.build());
     }
 
     private void createBlockedNotification(PrintJobInfo printJob) {
         Notification.Builder builder = new Notification.Builder(mContext)
                 .setContentIntent(createContentIntent(printJob.getId()))
-                .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
-                .setContentTitle(mContext.getString(R.string.blocked_notification_title_template,
-                        printJob.getLabel()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
                         createCancelIntent(printJob))
                 .setContentText(printJob.getPrinterName())
                 .setWhen(System.currentTimeMillis())
                 .setOngoing(true)
                 .setShowWhen(true);
-        mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+           mNotificationManager.notify(0, builder.build());
     }
 
-    private void removeNotification(PrintJobId printJobId) {
-        mNotificationManager.cancel(printJobId.flattenToString(), 0);
+    private void createCancellingNotification(PrintJobInfo printJob) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(printJob.getId()))
+                .setSmallIcon(computeNotificationIcon(printJob))
+                .setContentTitle(computeNotificationTitle(printJob))
+                .setContentText(printJob.getPrinterName())
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private void createStackedNotification(List<PrintJobInfo> printJobs) {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setContentIntent(createContentIntent(null))
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .setShowWhen(true);
+
+        final int printJobCount = printJobs.size();
+
+        InboxStyle inboxStyle = new InboxStyle();
+        inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
+                R.plurals.composite_notification_title_template,
+                printJobCount).toString(), printJobCount));
+
+        for (int i = printJobCount - 1; i>= 0; i--) {
+            PrintJobInfo printJob = printJobs.get(i);
+            if (i == printJobCount - 1) {
+                builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
+                        computeNotificationIcon(printJob))).getBitmap());
+                builder.setSmallIcon(computeNotificationIcon(printJob));
+                builder.setContentTitle(computeNotificationTitle(printJob));
+                builder.setContentText(printJob.getPrinterName());
+            }
+            inboxStyle.addLine(computeNotificationTitle(printJob));
+        }
+
+        builder.setNumber(printJobCount);
+        builder.setStyle(inboxStyle);
+
+        mNotificationManager.notify(0, builder.build());
+    }
+
+    private String computeNotificationTitle(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_FAILED: {
+                return mContext.getString(R.string.failed_notification_title_template,
+                        printJob.getLabel());
+            }
+
+            case PrintJobInfo.STATE_BLOCKED: {
+                if (!printJob.isCancelling()) {
+                    return mContext.getString(R.string.blocked_notification_title_template,
+                            printJob.getLabel());
+                } else {
+                    return mContext.getString(
+                            R.string.cancelling_notification_title_template,
+                            printJob.getLabel());
+                }
+            }
+
+            default: {
+                if (!printJob.isCancelling()) {
+                    return mContext.getString(R.string.printing_notification_title_template,
+                            printJob.getLabel());
+                } else {
+                    return mContext.getString(
+                            R.string.cancelling_notification_title_template,
+                            printJob.getLabel());
+                }
+            }
+        }
+    }
+
+    private void removeNotification() {
+        mNotificationManager.cancel(0);
     }
 
     private PendingIntent createContentIntent(PrintJobId printJobId) {
         Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
-        intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
-        return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+        if (printJobId != null) {
+            intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
+            intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null));
+        }
+        return PendingIntent.getActivity(mContext, 0, intent, 0);
     }
 
     private PendingIntent createCancelIntent(PrintJobInfo printJob) {
@@ -160,6 +262,36 @@
         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
     }
 
+    private static boolean shouldNotifyForState(int state) {
+        switch (state) {
+            case PrintJobInfo.STATE_QUEUED:
+            case PrintJobInfo.STATE_STARTED:
+            case PrintJobInfo.STATE_FAILED:
+            case PrintJobInfo.STATE_COMPLETED:
+            case PrintJobInfo.STATE_CANCELED:
+            case PrintJobInfo.STATE_BLOCKED: {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int computeNotificationIcon(PrintJobInfo printJob) {
+        switch (printJob.getState()) {
+            case PrintJobInfo.STATE_FAILED:
+            case PrintJobInfo.STATE_BLOCKED: {
+                return com.android.internal.R.drawable.ic_print_error;
+            }
+            default: {
+                if (!printJob.isCancelling()) {
+                    return com.android.internal.R.drawable.ic_print;
+                } else {
+                    return R.drawable.stat_notify_cancelling;
+                }
+            }
+        }
+    }
+
     public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
         private static final String LOG_TAG = "NotificationBroadcastReceiver";
 
@@ -183,20 +315,6 @@
                 Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
             }
 
-            // Put up a notification that we are trying to cancel.
-            NotificationManager notificationManager = (NotificationManager)
-                    context.getSystemService(Context.NOTIFICATION_SERVICE);
-            Notification.Builder builder = new Notification.Builder(context)
-                    .setSmallIcon(R.drawable.stat_notify_cancelling)
-                    .setContentTitle(context.getString(
-                            R.string.cancelling_notification_title_template,
-                            printJobLabel))
-                    .setContentText(printerName)
-                    .setWhen(System.currentTimeMillis())
-                    .setOngoing(true)
-                    .setShowWhen(true);
-            notificationManager.notify(printJobId.flattenToString(), 0, builder.build());
-
             // Call into the print manager service off the main thread since
             // the print manager service may end up binding to the print spooler
             // service which binding is handled on the main thread.
@@ -217,8 +335,8 @@
                     // done on another thread and until it finishes the spooler has
                     // to be kept around.
                     try {
-                    IPrintManager printManager = IPrintManager.Stub.asInterface(
-                            ServiceManager.getService(Context.PRINT_SERVICE));
+                        IPrintManager printManager = IPrintManager.Stub.asInterface(
+                                ServiceManager.getService(Context.PRINT_SERVICE));
                         printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
                                 UserHandle.myUserId());
                     } catch (RemoteException re) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
index 6dd8aa0a..c1c4d21 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintDialogFrame.java
@@ -24,6 +24,8 @@
 
     public final int mMaxWidth;
 
+    public int mHeight;
+
     public PrintDialogFrame(Context context, AttributeSet attrs) {
         super(context, attrs);
         mMaxWidth = context.getResources().getDimensionPixelSize(
@@ -32,13 +34,36 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        if (widthMode == MeasureSpec.AT_MOST) {
-            final int receivedWidth = MeasureSpec.getSize(widthMeasureSpec);
-            final int computedWidth = Math.min(mMaxWidth, receivedWidth);
-            widthMeasureSpec = MeasureSpec.makeMeasureSpec(computedWidth,
-                    MeasureSpec.EXACTLY);
-        }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int measuredWidth  = getMeasuredWidth();
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        switch (widthMode) {
+            case MeasureSpec.UNSPECIFIED: {
+                measuredWidth = mMaxWidth;
+            } break;
+
+            case MeasureSpec.AT_MOST: {
+                final int receivedWidth = MeasureSpec.getSize(widthMeasureSpec);
+                measuredWidth = Math.min(mMaxWidth, receivedWidth);
+            } break;
+        }
+
+        mHeight = Math.max(mHeight, getMeasuredHeight());
+
+        int measuredHeight  = getMeasuredHeight();
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        switch (heightMode) {
+            case MeasureSpec.UNSPECIFIED: {
+                measuredHeight = mHeight;
+            } break;
+
+             case MeasureSpec.AT_MOST: {
+                final int receivedHeight = MeasureSpec.getSize(heightMeasureSpec);
+                measuredHeight = Math.min(mHeight, receivedHeight);
+            } break;
+        }
+
+        setMeasuredDimension(measuredWidth, measuredHeight);
     }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 0bd8344..787b59a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -19,9 +19,11 @@
 import android.app.Activity;
 import android.app.Dialog;
 import android.app.LoaderManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
+import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.DataSetObserver;
@@ -38,6 +40,7 @@
 import android.os.RemoteException;
 import android.print.ILayoutResultCallback;
 import android.print.IPrintDocumentAdapter;
+import android.print.IPrintDocumentAdapterObserver;
 import android.print.IWriteResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -52,6 +55,7 @@
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
 import android.print.PrinterInfo;
+import android.provider.DocumentsContract;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -59,14 +63,17 @@
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AdapterView;
@@ -75,12 +82,15 @@
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Spinner;
 import android.widget.TextView;
 
 import com.android.printspooler.MediaSizeUtils.MediaSizeComparator;
 
+import libcore.io.IoUtils;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -97,8 +107,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import libcore.io.IoUtils;
-
 /**
  * Activity for configuring a print job.
  */
@@ -108,9 +116,6 @@
 
     private static final boolean DEBUG = false;
 
-    public static final String EXTRA_PRINT_DOCUMENT_ADAPTER = "printDocumentAdapter";
-    public static final String EXTRA_PRINT_JOB = "printJob";
-
     public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
 
     private static final int LOADER_ID_PRINTERS_LOADER = 1;
@@ -174,6 +179,10 @@
 
     private Dialog mGeneratingPrintJobDialog;
 
+    private PrintSpoolerProvider mSpoolerProvider;
+
+    private String mCallingPackageName;
+
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
@@ -182,29 +191,33 @@
 
         Bundle extras = getIntent().getExtras();
 
-        PrintJobInfo printJob = extras.getParcelable(EXTRA_PRINT_JOB);
+        PrintJobInfo printJob = extras.getParcelable(PrintManager.EXTRA_PRINT_JOB);
         if (printJob == null) {
             throw new IllegalArgumentException("printJob cannot be null");
         }
 
         mPrintJobId = printJob.getId();
-        mIPrintDocumentAdapter = extras.getBinder(EXTRA_PRINT_DOCUMENT_ADAPTER);
+        mIPrintDocumentAdapter = extras.getBinder(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER);
         if (mIPrintDocumentAdapter == null) {
             throw new IllegalArgumentException("PrintDocumentAdapter cannot be null");
         }
 
+        try {
+            IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter)
+                    .setObserver(new PrintDocumentAdapterObserver(this));
+        } catch (RemoteException re) {
+            finish();
+            return;
+        }
+
         PrintAttributes attributes = printJob.getAttributes();
         if (attributes != null) {
             mCurrPrintAttributes.copyFrom(attributes);
         }
 
-        setContentView(R.layout.print_job_config_activity_container);
+        mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
 
-        mDocument = new Document();
-        mController = new PrintController(new RemotePrintDocumentAdapter(
-                IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
-        mEditor = new Editor();
+        setContentView(R.layout.print_job_config_activity_container);
 
         try {
             mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
@@ -213,14 +226,31 @@
             return;
         }
 
-        mController.initialize();
-        mEditor.initialize();
+        mDocument = new Document();
+        mEditor = new Editor();
+
+        mSpoolerProvider = new PrintSpoolerProvider(this,
+                new Runnable() {
+            @Override
+            public void run() {
+                // We got the spooler so unleash the UI.
+                mController = new PrintController(new RemotePrintDocumentAdapter(
+                        IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
+                        mSpoolerProvider.getSpooler().generateFileForPrintJob(mPrintJobId)));
+                mController.initialize();
+
+                mEditor.initialize();
+                mEditor.postCreate();
+            }
+        });
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        mEditor.refreshCurrentPrinter();
+        if (mSpoolerProvider.getSpooler() != null) {
+            mEditor.refreshCurrentPrinter();
+        }
     }
 
     @Override
@@ -228,26 +258,29 @@
         // We can safely do the work in here since at this point
         // the system is bound to our (spooler) process which
         // guarantees that this process will not be killed.
-        if (mController.hasStarted()) {
+        if (mController != null && mController.hasStarted()) {
             mController.finish();
         }
-        if (mEditor.isPrintConfirmed() && mController.isFinished()) {
-            PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
-                    PrintJobInfo.STATE_QUEUED, null);
+        if (mEditor != null && mEditor.isPrintConfirmed()
+                && mController != null && mController.isFinished()) {
+                mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
+                        PrintJobInfo.STATE_QUEUED, null);
         } else {
-            PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
+            mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_CANCELED, null);
         }
-        mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
         if (mGeneratingPrintJobDialog != null) {
             mGeneratingPrintJobDialog.dismiss();
             mGeneratingPrintJobDialog = null;
         }
+        mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
+        mSpoolerProvider.destroy();
         super.onDestroy();
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        if (!mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) {
+        if (mController != null && mEditor != null &&
+                !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) {
             if (!mController.isWorking()) {
                 PrintJobConfigActivity.this.finish();
             }
@@ -265,17 +298,19 @@
     }
 
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            if (mEditor.isShwoingGeneratingPrintJobUi()) {
+        if (mController != null && mEditor != null) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                if (mEditor.isShwoingGeneratingPrintJobUi()) {
+                    return true;
+                }
+                if (event.isTracking() && !event.isCanceled()) {
+                    if (!mController.isWorking()) {
+                        PrintJobConfigActivity.this.finish();
+                    }
+                }
+                mEditor.cancel();
                 return true;
             }
-            if (event.isTracking() && !event.isCanceled()) {
-                if (!mController.isWorking()) {
-                    PrintJobConfigActivity.this.finish();
-                }
-            }
-            mEditor.cancel();
-            return true;
         }
         return super.onKeyUp(keyCode, event);
     }
@@ -336,6 +371,10 @@
             return mControllerState >= CONTROLLER_STATE_LAYOUT_COMPLETED;
         }
 
+        public boolean isPerformingLayout() {
+            return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED;
+        }
+
         public boolean isWorking() {
             return mControllerState == CONTROLLER_STATE_LAYOUT_STARTED
                     || mControllerState == CONTROLLER_STATE_WRITE_STARTED;
@@ -351,9 +390,18 @@
             if (!mController.hasStarted()) {
                 mController.start();
             }
+
+            // If the print attributes are the same and we are performing
+            // a layout, then we have to wait for it to completed which will
+            // trigger writing of the necessary pages.
+            final boolean printAttributesChanged = printAttributesChanged();
+            if (!printAttributesChanged && isPerformingLayout()) {
+                return;
+            }
+
             // If print is confirmed we always do a layout since the previous
             // ones were for preview and this one is for printing.
-            if (!printAttributesChanged() && !mEditor.isPrintConfirmed()) {
+            if (!printAttributesChanged && !mEditor.isPrintConfirmed()) {
                 if (mDocument.info == null) {
                     // We are waiting for the result of a layout, so do nothing.
                     return;
@@ -364,7 +412,7 @@
                 // we handle writing as usual.
                 handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
             } else {
-                PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence(
+                mSpoolerProvider.getSpooler().setPrintJobAttributesNoPersistence(
                         mPrintJobId, mCurrPrintAttributes);
 
                 mMetadata.putBoolean(PrintDocumentAdapter.EXTRA_PRINT_PREVIEW,
@@ -409,7 +457,7 @@
             if (infoChanged) {
                 mDocument.info = info;
                 // Set the info.
-                PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+                mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
                         mPrintJobId, info);
             }
 
@@ -417,7 +465,7 @@
             // drop the pages since we have to fetch them again.
             if (infoChanged || layoutChanged) {
                 mDocument.pages = null;
-                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(
+                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(
                         mPrintJobId, null);
             }
 
@@ -456,6 +504,7 @@
                 // Then update the print jobs's pages as we will not do a write
                 // and we usually update the pages in the write complete callback.
                 updatePrintJobPages(mDocument.pages, mRequestedPages);
+                mEditor.updateUi();
                 if (mEditor.isDone()) {
                     requestCreatePdfFileOrFinish();
                 }
@@ -470,14 +519,20 @@
                     mRequestCounter.incrementAndGet());
         }
 
-        private void handleOnLayoutFailed(CharSequence error, int sequence) {
+        private void handleOnLayoutFailed(final CharSequence error, int sequence) {
             if (mRequestCounter.get() != sequence) {
                 return;
             }
             mControllerState = CONTROLLER_STATE_FAILED;
-            // TODO: We need some UI for announcing an error.
-            Log.e(LOG_TAG, "Error during layout: " + error);
-            PrintJobConfigActivity.this.finish();
+            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
+                @Override
+                public void run() {
+                    if (!TextUtils.isEmpty(error)) {
+                        TextView messageView = (TextView) findViewById(R.id.message);
+                        messageView.setText(error);
+                    }
+                }
+            });
         }
 
         private void handleOnWriteFinished(PageRange[] pages, int sequence) {
@@ -495,12 +550,12 @@
             mControllerState = CONTROLLER_STATE_WRITE_COMPLETED;
 
             // Update the document size.
-            File file = PrintSpoolerService.peekInstance()
+            File file = mSpoolerProvider.getSpooler()
                     .generateFileForPrintJob(mPrintJobId);
             mDocument.info.setDataSize(file.length());
 
             // Update the print job with the updated info.
-            PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+            mSpoolerProvider.getSpooler().setPrintJobPrintDocumentInfoNoPersistence(
                     mPrintJobId, mDocument.info);
 
             // Update which pages we have fetched.
@@ -524,12 +579,12 @@
             if (Arrays.equals(writtenPages, requestedPages)) {
                 // We got a document with exactly the pages we wanted. Hence,
                 // the printer has to print all pages in the data.
-                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
                         ALL_PAGES_ARRAY);
             } else if (Arrays.equals(writtenPages, ALL_PAGES_ARRAY)) {
                 // We requested specific pages but got all of them. Hence,
                 // the printer has to print only the requested pages.
-                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
                         requestedPages);
             } else if (PageRangeUtils.contains(writtenPages, requestedPages)) {
                 // We requested specific pages and got more but not all pages.
@@ -539,7 +594,7 @@
                 final int offset = -writtenPages[0].getStart();
                 PageRange[] offsetPages = Arrays.copyOf(requestedPages, requestedPages.length);
                 PageRangeUtils.offset(offsetPages, offset);
-                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
                         offsetPages);
             } else if (Arrays.equals(requestedPages, ALL_PAGES_ARRAY)
                     && writtenPages.length == 1 && writtenPages[0].getStart() == 0
@@ -547,7 +602,7 @@
                 // We requested all pages via the special constant and got all
                 // of them as an explicit enumeration. Hence, the printer has
                 // to print only the requested pages.
-                PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence(mPrintJobId,
                         writtenPages);
             } else {
                 // We did not get the pages we requested, then the application
@@ -562,24 +617,32 @@
 
         private void requestCreatePdfFileOrFinish() {
             if (mEditor.isPrintingToPdf()) {
-                PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+                PrintJobInfo printJob = mSpoolerProvider.getSpooler()
                         .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
                 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
                 intent.setType("application/pdf");
                 intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel());
+                intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
                 startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
             } else {
                 PrintJobConfigActivity.this.finish();
             }
         }
 
-        private void handleOnWriteFailed(CharSequence error, int sequence) {
+        private void handleOnWriteFailed(final CharSequence error, int sequence) {
             if (mRequestCounter.get() != sequence) {
                 return;
             }
             mControllerState = CONTROLLER_STATE_FAILED;
-            Log.e(LOG_TAG, "Error during write: " + error);
-            PrintJobConfigActivity.this.finish();
+            mEditor.showUi(Editor.UI_ERROR, new Runnable() {
+                @Override
+                public void run() {
+                    if (!TextUtils.isEmpty(error)) {
+                        TextView messageView = (TextView) findViewById(R.id.message);
+                        messageView.setText(error);
+                    }
+                }
+            });
         }
 
         private boolean equalsIgnoreSize(PrintDocumentInfo lhs, PrintDocumentInfo rhs) {
@@ -710,6 +773,7 @@
                         public void run() {
                             mEditor.initialize();
                             mEditor.bindUi();
+                            mEditor.reselectCurrentPrinter();
                             mEditor.updateUi();
                         }
                     });
@@ -737,12 +801,12 @@
                 InputStream in = null;
                 OutputStream out = null;
                 try {
-                    PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+                    PrintJobInfo printJob = mSpoolerProvider.getSpooler()
                             .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
                     if (printJob == null) {
                         return null;
                     }
-                    File file = PrintSpoolerService.peekInstance()
+                    File file = mSpoolerProvider.getSpooler()
                             .generateFileForPrintJob(mPrintJobId);
                     in = new FileInputStream(file);
                     out = getContentResolver().openOutputStream(uri);
@@ -777,6 +841,7 @@
         private static final int UI_NONE = 0;
         private static final int UI_EDITING_PRINT_JOB = 1;
         private static final int UI_GENERATING_PRINT_JOB = 2;
+        private static final int UI_ERROR = 3;
 
         private EditText mCopiesEditText;
 
@@ -785,21 +850,21 @@
         private EditText mPageRangeEditText;
 
         private Spinner mDestinationSpinner;
-        private final DestinationAdapter mDestinationSpinnerAdapter;
+        private DestinationAdapter mDestinationSpinnerAdapter;
 
         private Spinner mMediaSizeSpinner;
-        private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
+        private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
 
         private Spinner mColorModeSpinner;
-        private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
+        private ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
 
         private Spinner mOrientationSpinner;
-        private final  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
+        private  ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
 
         private Spinner mRangeOptionsSpinner;
-        private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
+        private ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
 
-        private final SimpleStringSplitter mStringCommaSplitter =
+        private SimpleStringSplitter mStringCommaSplitter =
                 new SimpleStringSplitter(',');
 
         private View mContentContainer;
@@ -810,7 +875,17 @@
 
         private PrinterInfo mCurrentPrinter;
 
-        private final MediaSizeComparator mMediaSizeComparator;
+        private MediaSizeComparator mMediaSizeComparator;
+
+        private final OnFocusChangeListener mFocusListener = new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean hasFocus) {
+                EditText editText = (EditText) view;
+                if (!TextUtils.isEmpty(editText.getText())) {
+                    editText.setSelection(editText.getText().length());
+                }
+            }
+        };
 
         private final OnItemSelectedListener mOnItemSelectedListener =
                 new AdapterView.OnItemSelectedListener() {
@@ -822,6 +897,11 @@
                         return;
                     }
 
+                    if (position == AdapterView.INVALID_POSITION) {
+                        updateUi();
+                        return;
+                    }
+
                     if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
                         startSelectPrinterActivity();
                         return;
@@ -832,7 +912,7 @@
                     mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
                             .getItem(position);
 
-                    PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
+                    mSpoolerProvider.getSpooler().setPrintJobPrinterNoPersistence(
                             mPrintJobId, mCurrentPrinter);
 
                     if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
@@ -1049,7 +1129,7 @@
                 }
 
                 mCopiesEditText.setError(null);
-                PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+                mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
                         mPrintJobId, copies);
                 updateUi();
 
@@ -1141,6 +1221,10 @@
         private boolean mFavoritePrinterSelected;
 
         public Editor() {
+            showUi(UI_EDITING_PRINT_JOB, null);
+        }
+
+        public void postCreate() {
             // Destination.
             mMediaSizeComparator = new MediaSizeComparator(PrintJobConfigActivity.this);
             mDestinationSpinnerAdapter = new DestinationAdapter();
@@ -1177,17 +1261,39 @@
                                     continue;
                                 }
 
+                                // If nothing changed - done.
+                                if (mCurrentPrinter.equals(printer)) {
+                                    return;
+                                }
+
                                 // If the current printer became available and has no
                                 // capabilities, we refresh it.
                                 if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
                                         && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
-                                        && printer.getCapabilities() == null
-                                        && !mCapabilitiesTimeout.isPosted()) {
-                                    mCapabilitiesTimeout.post();
+                                        && printer.getCapabilities() == null) {
+                                    if (!mCapabilitiesTimeout.isPosted()) {
+                                        mCapabilitiesTimeout.post();
+                                    }
+                                    mCurrentPrinter.copyFrom(printer);
                                     refreshCurrentPrinter();
                                     return;
                                 }
 
+                                // If the current printer became unavailable or its
+                                // capabilities go away, we update the UI and add a
+                                // timeout to declare the printer as unavailable.
+                                if ((mCurrentPrinter.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
+                                        && printer.getStatus() == PrinterInfo.STATUS_UNAVAILABLE)
+                                    || (mCurrentPrinter.getCapabilities() != null
+                                        && printer.getCapabilities() == null)) {
+                                    if (!mCapabilitiesTimeout.isPosted()) {
+                                        mCapabilitiesTimeout.post();
+                                    }
+                                    mCurrentPrinter.copyFrom(printer);
+                                    updateUi();
+                                    return;
+                                }
+
                                 // We just refreshed the current printer.
                                 if (printer.getCapabilities() != null
                                         && mCapabilitiesTimeout.isPosted()) {
@@ -1278,6 +1384,21 @@
             updateUi();
         }
 
+        public void reselectCurrentPrinter() {
+            if (mCurrentPrinter != null) {
+                // TODO: While the data did not change and we set the adapter
+                // to a newly inflated spinner, the latter does not show the
+                // current item unless we poke the adapter. This requires more
+                // investigation. Maybe an optimization in AdapterView does not
+                // call into the adapter if the view is not visible which is the
+                // case when we set the adapter.
+                mDestinationSpinnerAdapter.notifyDataSetChanged();
+                final int position = mDestinationSpinnerAdapter.getPrinterIndex(
+                        mCurrentPrinter.getId());
+                mDestinationSpinner.setSelection(position);
+            }
+        }
+
         public void refreshCurrentPrinter() {
             PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
             if (printer != null) {
@@ -1375,7 +1496,10 @@
                 return;
             }
 
-            switch (mCurrentUi) {
+            final int oldUi = mCurrentUi;
+            mCurrentUi = ui;
+
+            switch (oldUi) {
                 case UI_NONE: {
                     switch (ui) {
                         case UI_EDITING_PRINT_JOB: {
@@ -1408,7 +1532,24 @@
                                         postSwitchCallback.run();
                                     }
                                 }
-                            });
+                            },
+                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
+                        } break;
+
+                        case UI_ERROR: {
+                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    registerOkButtonClickListener();
+                                    if (postSwitchCallback != null) {
+                                        postSwitchCallback.run();
+                                    }
+                                }
+                            },
+                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
                         } break;
                     }
                 } break;
@@ -1425,13 +1566,47 @@
                                         postSwitchCallback.run();
                                     }
                                 }
-                            });
+                            },
+                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
+                        } break;
+
+                        case UI_ERROR: {
+                            animateUiSwitch(R.layout.print_job_config_activity_content_error,
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    registerOkButtonClickListener();
+                                    if (postSwitchCallback != null) {
+                                        postSwitchCallback.run();
+                                    }
+                                }
+                            },
+                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
+                        } break;
+                    }
+                } break;
+
+                case UI_ERROR: {
+                    switch (ui) {
+                        case UI_EDITING_PRINT_JOB: {
+                            animateUiSwitch(R.layout.print_job_config_activity_content_editing,
+                                    new Runnable() {
+                                @Override
+                                public void run() {
+                                    registerPrintButtonClickListener();
+                                    if (postSwitchCallback != null) {
+                                        postSwitchCallback.run();
+                                    }
+                                }
+                            },
+                            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
                         } break;
                     }
                 } break;
             }
-
-            mCurrentUi = ui;
         }
 
         private void registerPrintButtonClickListener() {
@@ -1467,13 +1642,34 @@
             });
         }
 
+        private void registerOkButtonClickListener() {
+            Button okButton = (Button) findViewById(R.id.ok_button);
+            okButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mEditor.showUi(Editor.UI_EDITING_PRINT_JOB, new Runnable() {
+                        @Override
+                        public void run() {
+                            // Start over with a clean slate.
+                            mOldPrintAttributes.clear();
+                            mController.initialize();
+                            mEditor.initialize();
+                            mEditor.bindUi();
+                            mEditor.reselectCurrentPrinter();
+                        }
+                    });
+                }
+            });
+        }
+
         private void doUiSwitch(int showLayoutId) {
             ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
             contentContainer.removeAllViews();
             getLayoutInflater().inflate(showLayoutId, contentContainer, true);
         }
 
-        private void animateUiSwitch(int showLayoutId, final Runnable postAnimateCommand) {
+        private void animateUiSwitch(int showLayoutId, final Runnable beforeShowNewUiAction,
+                final LayoutParams containerParams) {
             // Find everything we will shuffle around.
             final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
             final View hidingView = contentContainer.getChildAt(0);
@@ -1501,7 +1697,7 @@
                             / (float) contentContainer.getHeight();
 
                     // Second animation - resize the container.
-                    AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY).withLayer()
+                    AutoCancellingAnimator.animate(contentContainer).scaleY(scaleY)
                             .withEndAction(new Runnable() {
                         @Override
                         public void run() {
@@ -1510,14 +1706,12 @@
                             contentContainer.setScaleY(1.0f);
                             contentContainer.addView(showingView);
 
+                            contentContainer.setLayoutParams(containerParams);
+
+                            beforeShowNewUiAction.run();
+
                             // Third animation - show the new content.
-                            AutoCancellingAnimator.animate(showingView).withLayer().alpha(1.0f)
-                                    .withEndAction(new Runnable() {
-                                @Override
-                                public void run() {
-                                    postAnimateCommand.run();
-                                }
-                            });
+                            AutoCancellingAnimator.animate(showingView).alpha(1.0f);
                         }
                     });
                 }
@@ -1605,12 +1799,14 @@
 
             // Copies
             mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+            mCopiesEditText.setOnFocusChangeListener(mFocusListener);
             mCopiesEditText.setText(MIN_COPIES_STRING);
+            mCopiesEditText.setSelection(mCopiesEditText.getText().length());
             mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
             if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
                 mIgnoreNextCopiesChange = true;
             }
-            PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+            mSpoolerProvider.getSpooler().setPrintJobCopiesNoPersistence(
                     mPrintJobId, MIN_COPIES);
 
             // Destination.
@@ -1618,7 +1814,7 @@
             mDestinationSpinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT);
             mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
             mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-            if (mDestinationSpinnerAdapter.getCount() > 0 && mController.hasStarted()) {
+            if (mDestinationSpinnerAdapter.getCount() > 0) {
                 mIgnoreNextDestinationChange = true;
             }
 
@@ -1658,6 +1854,7 @@
             // Page range
             mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
             mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+            mPageRangeEditText.setOnFocusChangeListener(mFocusListener);
             mPageRangeEditText.addTextChangedListener(mRangeTextWatcher);
 
             // Print button
@@ -1991,10 +2188,17 @@
                 mIsPosted = false;
                 if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
                     View itemView = mDestinationSpinner.getSelectedView();
-                    TextView titleView = (TextView) itemView.findViewById(R.id.title);
-                    String title = getString(R.string.printer_unavailable,
-                            mCurrentPrinter.getName());
-                    titleView.setText(title);
+                    TextView titleView = (TextView) itemView.findViewById(R.id.subtitle);
+                    try {
+                        PackageInfo packageInfo = getPackageManager().getPackageInfo(
+                                mCurrentPrinter.getId().getServiceName().getPackageName(), 0);
+                        CharSequence service = packageInfo.applicationInfo.loadLabel(
+                                getPackageManager());
+                        String subtitle = getString(R.string.printer_unavailable, service.toString());
+                        titleView.setText(subtitle);
+                    } catch (NameNotFoundException nnfe) {
+                        /* ignore */
+                    }
                 }
             }
         }
@@ -2040,9 +2244,10 @@
 
             @Override
             public int getCount() {
-                final int additionalItemCount = (mFakePdfPrinter != null) ? 2 : 1;
-                return Math.min(mPrinters.size() + additionalItemCount,
-                        DEST_ADAPTER_MAX_ITEM_COUNT);
+                if (mFakePdfPrinter == null) {
+                    return 0;
+                }
+                return Math.min(mPrinters.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
             }
 
             @Override
@@ -2078,11 +2283,12 @@
             @Override
             public long getItemId(int position) {
                 if (mPrinters.isEmpty()) {
-                    if (position == 0 && mFakePdfPrinter != null) {
-                        return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
-                    }
-                    if (position == 1) {
-                        return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                    if (mFakePdfPrinter != null) {
+                        if (position == 0) {
+                            return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+                        } else if (position == 1) {
+                            return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+                        }
                     }
                 } else {
                     if (position == 1 && mFakePdfPrinter != null) {
@@ -2110,8 +2316,6 @@
                             R.layout.printer_dropdown_item, parent, false);
                 }
 
-                convertView.getLayoutParams().width = mDestinationSpinner.getWidth();
-
                 CharSequence title = null;
                 CharSequence subtitle = null;
                 Drawable icon = null;
@@ -2160,7 +2364,7 @@
                     iconView.setImageDrawable(icon);
                     iconView.setVisibility(View.VISIBLE);
                 } else {
-                    iconView.setVisibility(View.GONE);
+                    iconView.setVisibility(View.INVISIBLE);
                 }
 
                 return convertView;
@@ -2473,4 +2677,69 @@
             }
         }
     }
+
+    private static final class PrintSpoolerProvider implements ServiceConnection {
+        private final Context mContext;
+        private final Runnable mCallback;
+
+        private PrintSpoolerService mSpooler;
+
+        public PrintSpoolerProvider(Context context, Runnable callback) {
+            mContext = context;
+            mCallback = callback;
+            Intent intent = new Intent(mContext, PrintSpoolerService.class);
+            mContext.bindService(intent, this, 0);
+        }
+
+        public PrintSpoolerService getSpooler() {
+            return mSpooler;
+        }
+
+        public void destroy() {
+            if (mSpooler != null) {
+                mContext.unbindService(this);
+            }
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mSpooler = ((PrintSpoolerService.PrintSpooler) service).getService();
+            if (mSpooler != null) {
+                mCallback.run();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            /* do noting - we are in the same process */
+        }
+    }
+
+    private static final class PrintDocumentAdapterObserver
+            extends IPrintDocumentAdapterObserver.Stub {
+        private final WeakReference<PrintJobConfigActivity> mWeakActvity;
+
+        public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) {
+            mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity);
+        }
+
+        @Override
+        public void onDestroy() {
+            final PrintJobConfigActivity activity = mWeakActvity.get();
+            if (activity != null) {
+                activity.mController.mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (activity.mController != null) {
+                            activity.mController.cancel();
+                        }
+                        if (activity.mEditor != null) {
+                            activity.mEditor.cancel();
+                        }
+                        activity.finish();
+                    }
+                });
+            }
+        }
+    }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index d1b42bc..636e245 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -16,18 +16,14 @@
 
 package com.android.printspooler;
 
-import android.app.PendingIntent;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.content.IntentSender;
 import android.os.AsyncTask;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.print.IPrintClient;
-import android.print.IPrintDocumentAdapter;
 import android.print.IPrintSpooler;
 import android.print.IPrintSpoolerCallbacks;
 import android.print.IPrintSpoolerClient;
@@ -50,7 +46,6 @@
 import android.util.Xml;
 
 import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastXmlSerializer;
 
 import libcore.io.IoUtils;
@@ -132,105 +127,7 @@
 
     @Override
     public IBinder onBind(Intent intent) {
-        return new IPrintSpooler.Stub() {
-            @Override
-            public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
-                    ComponentName componentName, int state, int appId, int sequence)
-                    throws RemoteException {
-                List<PrintJobInfo> printJobs = null;
-                try {
-                    printJobs = PrintSpoolerService.this.getPrintJobInfos(
-                            componentName, state, appId);
-                } finally {
-                    callback.onGetPrintJobInfosResult(printJobs, sequence);
-                }
-            }
-
-            @Override
-            public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
-                    int appId, int sequence) throws RemoteException {
-                PrintJobInfo printJob = null;
-                try {
-                    printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
-                } finally {
-                    callback.onGetPrintJobInfoResult(printJob, sequence);
-                }
-            }
-
-            @SuppressWarnings("deprecation")
-            @Override
-            public void createPrintJob(PrintJobInfo printJob, IPrintClient client,
-                IPrintDocumentAdapter printAdapter) throws RemoteException {
-                PrintSpoolerService.this.createPrintJob(printJob);
-
-                Intent intent = new Intent(printJob.getId().flattenToString());
-                intent.setClass(PrintSpoolerService.this, PrintJobConfigActivity.class);
-                intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
-                        printAdapter.asBinder());
-                intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_JOB, printJob);
-
-                IntentSender sender = PendingIntent.getActivity(
-                        PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
-                        | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
-
-                Message message = mHandlerCaller.obtainMessageO(
-                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
-                        printJob);
-                mHandlerCaller.executeOrSendMessage(message);
-
-                message = mHandlerCaller.obtainMessageOO(
-                        HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
-                        client, sender);
-                mHandlerCaller.executeOrSendMessage(message);
-
-                printJob.setCreationTime(System.currentTimeMillis());
-            }
-
-            @Override
-            public void setPrintJobState(PrintJobId printJobId, int state, String error,
-                    IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
-                boolean success = false;
-                try {
-                    success = PrintSpoolerService.this.setPrintJobState(
-                            printJobId, state, error);
-                } finally {
-                    callback.onSetPrintJobStateResult(success, sequece);
-                }
-            }
-
-            @Override
-            public void setPrintJobTag(PrintJobId printJobId, String tag,
-                    IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
-                boolean success = false;
-                try {
-                    success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
-                } finally {
-                    callback.onSetPrintJobTagResult(success, sequece);
-                }
-            }
-
-            @Override
-            public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
-                PrintSpoolerService.this.writePrintJobData(fd, printJobId);
-            }
-
-            @Override
-            public void setClient(IPrintSpoolerClient client) {
-                Message message = mHandlerCaller.obtainMessageO(
-                        HandlerCallerCallback.MSG_SET_CLIENT, client);
-                mHandlerCaller.executeOrSendMessage(message);
-            }
-
-            @Override
-            public void removeObsoletePrintJobs() {
-                PrintSpoolerService.this.removeObsoletePrintJobs();
-            }
-
-            @Override
-            protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-                PrintSpoolerService.this.dump(fd, writer, args);
-            }
-        };
+        return new PrintSpooler();
     }
 
     @Override
@@ -281,12 +178,11 @@
 
     private final class HandlerCallerCallback implements HandlerCaller.Callback {
         public static final int MSG_SET_CLIENT = 1;
-        public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2;
-        public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
-        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 4;
-        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 5;
-        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 6;
-        public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 7;
+        public static final int MSG_ON_PRINT_JOB_QUEUED = 2;
+        public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 3;
+        public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 4;
+        public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 5;
+        public static final int MSG_ON_PRINT_JOB_STATE_CHANGED = 6;
 
         @Override
         public void executeMessage(Message message) {
@@ -303,18 +199,6 @@
                     }
                 } break;
 
-                case MSG_START_PRINT_JOB_CONFIG_ACTIVITY: {
-                    SomeArgs args = (SomeArgs) message.obj;
-                    IPrintClient client = (IPrintClient) args.arg1;
-                    IntentSender sender = (IntentSender) args.arg2;
-                    args.recycle();
-                    try {
-                        client.startPrintJobConfigActivity(sender);
-                    } catch (RemoteException re) {
-                        Slog.i(LOG_TAG, "Error starting print job config activity!", re);
-                    }
-                } break;
-
                 case MSG_ON_PRINT_JOB_QUEUED: {
                     PrintJobInfo printJob = (PrintJobInfo) message.obj;
                     if (mClient != null) {
@@ -384,7 +268,9 @@
                         || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
                             && isStateVisibleToUser(printJob.getState()))
                         || (state == PrintJobInfo.STATE_ANY_ACTIVE
-                            && isActiveState(printJob.getState()));
+                            && isActiveState(printJob.getState()))
+                        || (state == PrintJobInfo.STATE_ANY_SCHEDULED
+                            && isScheduledState(printJob.getState()));
                 if (sameComponent && sameAppId && sameState) {
                     if (foundPrintJobs == null) {
                         foundPrintJobs = new ArrayList<PrintJobInfo>();
@@ -421,6 +307,11 @@
         synchronized (mLock) {
             addPrintJobLocked(printJob);
             setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
+
+            Message message = mHandlerCaller.obtainMessageO(
+                    HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+                    printJob);
+            mHandlerCaller.executeOrSendMessage(message);
         }
     }
 
@@ -459,8 +350,6 @@
                 fileForJobMap.remove(printJob.getId());
             }
 
-            // Update the notification.
-            mNotificationController.onPrintJobStateChanged(printJob);
             switch (printJob.getState()) {
                 case PrintJobInfo.STATE_QUEUED:
                 case PrintJobInfo.STATE_STARTED:
@@ -475,6 +364,11 @@
             }
         }
 
+        if (!mPrintJobs.isEmpty()) {
+            // Update the notification.
+            mNotificationController.onUpdateNotifications(mPrintJobs);
+        }
+
         // Delete the orphan files.
         if (fileForJobMap != null) {
             final int orphanFileCount = fileForJobMap.size();
@@ -586,7 +480,7 @@
 
                 printJob.setState(state);
                 printJob.setStateReason(error);
-                mNotificationController.onPrintJobStateChanged(printJob);
+                printJob.setCancelling(false);
 
                 if (DEBUG_PRINT_JOB_LIFECYCLE) {
                     Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
@@ -626,6 +520,8 @@
                         HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
                         printJob);
                 mHandlerCaller.executeOrSendMessage(message);
+
+                mNotificationController.onUpdateNotifications(mPrintJobs);
             }
         }
 
@@ -660,6 +556,12 @@
                 || printJobState == PrintJobInfo.STATE_QUEUED);
     }
 
+    private boolean isScheduledState(int printJobState) {
+        return printJobState == PrintJobInfo.STATE_QUEUED
+                || printJobState == PrintJobInfo.STATE_STARTED
+                || printJobState == PrintJobInfo.STATE_BLOCKED;
+    }
+
     private boolean isActiveState(int printJobState) {
         return printJobState == PrintJobInfo.STATE_CREATED
                 || printJobState == PrintJobInfo.STATE_QUEUED
@@ -694,6 +596,24 @@
         return false;
     }
 
+    public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+        synchronized (mLock) {
+            PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+            if (printJob != null) {
+                printJob.setCancelling(cancelling);
+                if (shouldPersistPrintJob(printJob)) {
+                    mPersistanceManager.writeStateLocked();
+                }
+                mNotificationController.onUpdateNotifications(mPrintJobs);
+
+                Message message = mHandlerCaller.obtainMessageO(
+                        HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+                        printJob);
+                mHandlerCaller.executeOrSendMessage(message);
+            }
+        }
+    }
+
     public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
         synchronized (mLock) {
             PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
@@ -777,12 +697,12 @@
         private static final String ATTR_PACKAGE_NAME = "packageName";
         private static final String ATTR_STATE = "state";
         private static final String ATTR_APP_ID = "appId";
-        private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_TAG = "tag";
         private static final String ATTR_CREATION_TIME = "creationTime";
         private static final String ATTR_COPIES = "copies";
         private static final String ATTR_PRINTER_NAME = "printerName";
         private static final String ATTR_STATE_REASON = "stateReason";
+        private static final String ATTR_CANCELLING = "cancelling";
 
         private static final String TAG_MEDIA_SIZE = "mediaSize";
         private static final String TAG_RESOLUTION = "resolution";
@@ -810,6 +730,7 @@
         private static final String ATTR_NAME = "name";
         private static final String ATTR_PAGE_COUNT = "pageCount";
         private static final String ATTR_CONTENT_TYPE = "contentType";
+        private static final String ATTR_DATA_SIZE = "dataSize";
 
         private final AtomicFile mStatePersistFile;
 
@@ -865,7 +786,6 @@
                     serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
                     serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
                     serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
-                    serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
                     String tag = printJob.getTag();
                     if (tag != null) {
                         serializer.attribute(null, ATTR_TAG, tag);
@@ -881,6 +801,8 @@
                     if (!TextUtils.isEmpty(stateReason)) {
                         serializer.attribute(null, ATTR_STATE_REASON, stateReason);
                     }
+                    serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
+                            printJob.isCancelling()));
 
                     PrinterId printerId = printJob.getPrinterId();
                     if (printerId != null) {
@@ -972,6 +894,8 @@
                                 documentInfo.getContentType()));
                         serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
                                 documentInfo.getPageCount()));
+                        serializer.attribute(null, ATTR_DATA_SIZE, String.valueOf(
+                                documentInfo.getDataSize()));
                         serializer.endTag(null, TAG_DOCUMENT_INFO);
                     }
 
@@ -1061,8 +985,6 @@
             printJob.setState(state);
             final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
             printJob.setAppId(appId);
-            final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
-            printJob.setUserId(userId);
             String tag = parser.getAttributeValue(null, ATTR_TAG);
             printJob.setTag(tag);
             String creationTime = parser.getAttributeValue(null, ATTR_CREATION_TIME);
@@ -1073,6 +995,9 @@
             printJob.setPrinterName(printerName);
             String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
             printJob.setStateReason(stateReason);
+            String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
+            printJob.setCancelling(!TextUtils.isEmpty(cancelling)
+                    ? Boolean.parseBoolean(cancelling) : false);
 
             parser.next();
 
@@ -1189,10 +1114,13 @@
                         ATTR_PAGE_COUNT));
                 final int contentType = Integer.parseInt(parser.getAttributeValue(null,
                         ATTR_CONTENT_TYPE));
+                final int dataSize = Integer.parseInt(parser.getAttributeValue(null,
+                        ATTR_DATA_SIZE));
                 PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
                         .setPageCount(pageCount)
                         .setContentType(contentType).build();
                 printJob.setDocumentInfo(info);
+                info.setDataSize(dataSize);
                 parser.next();
                 skipEmptyTextTags(parser);
                 expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
@@ -1243,4 +1171,89 @@
             return true;
         }
     }
+
+    final class PrintSpooler extends IPrintSpooler.Stub {
+        @Override
+        public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
+                ComponentName componentName, int state, int appId, int sequence)
+                throws RemoteException {
+            List<PrintJobInfo> printJobs = null;
+            try {
+                printJobs = PrintSpoolerService.this.getPrintJobInfos(
+                        componentName, state, appId);
+            } finally {
+                callback.onGetPrintJobInfosResult(printJobs, sequence);
+            }
+        }
+
+        @Override
+        public void getPrintJobInfo(PrintJobId printJobId, IPrintSpoolerCallbacks callback,
+                int appId, int sequence) throws RemoteException {
+            PrintJobInfo printJob = null;
+            try {
+                printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
+            } finally {
+                callback.onGetPrintJobInfoResult(printJob, sequence);
+            }
+        }
+
+        @Override
+        public void createPrintJob(PrintJobInfo printJob) {
+            PrintSpoolerService.this.createPrintJob(printJob);
+        }
+
+        @Override
+        public void setPrintJobState(PrintJobId printJobId, int state, String error,
+                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
+            boolean success = false;
+            try {
+                success = PrintSpoolerService.this.setPrintJobState(
+                        printJobId, state, error);
+            } finally {
+                callback.onSetPrintJobStateResult(success, sequece);
+            }
+        }
+
+        @Override
+        public void setPrintJobTag(PrintJobId printJobId, String tag,
+                IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
+            boolean success = false;
+            try {
+                success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
+            } finally {
+                callback.onSetPrintJobTagResult(success, sequece);
+            }
+        }
+
+        @Override
+        public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
+            PrintSpoolerService.this.writePrintJobData(fd, printJobId);
+        }
+
+        @Override
+        public void setClient(IPrintSpoolerClient client) {
+            Message message = mHandlerCaller.obtainMessageO(
+                    HandlerCallerCallback.MSG_SET_CLIENT, client);
+            mHandlerCaller.executeOrSendMessage(message);
+        }
+
+        @Override
+        public void removeObsoletePrintJobs() {
+            PrintSpoolerService.this.removeObsoletePrintJobs();
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+            PrintSpoolerService.this.dump(fd, writer, args);
+        }
+
+        @Override
+        public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+            PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
+        }
+
+        public PrintSpoolerService getService() {
+            return PrintSpoolerService.this;
+        }
+    }
 }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
index 7a91cef..204c152 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
@@ -22,7 +22,6 @@
 import android.app.DialogFragment;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
-import android.app.ListFragment;
 import android.app.LoaderManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -47,12 +46,14 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
+import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.BaseAdapter;
 import android.widget.Filter;
@@ -68,7 +69,7 @@
 /**
  * This is a fragment for selecting a printer.
  */
-public final class SelectPrinterFragment extends ListFragment {
+public final class SelectPrinterFragment extends Fragment {
 
     private static final String LOG_TAG = "SelectPrinterFragment";
 
@@ -83,6 +84,8 @@
     private final ArrayList<PrintServiceInfo> mAddPrinterServices =
             new ArrayList<PrintServiceInfo>();
 
+    private ListView mListView;
+
     private AnnounceFilterResult mAnnounceFilterResult;
 
     public static interface OnPrinterSelectedListener {
@@ -93,11 +96,16 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
+        getActivity().getActionBar().setIcon(R.drawable.ic_menu_print);
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View content = inflater.inflate(R.layout.select_printer_fragment, container, false);
+
+        // Hook up the list view.
+        mListView = (ListView) content.findViewById(android.R.id.list);
         final DestinationAdapter adapter = new DestinationAdapter();
         adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
@@ -114,7 +122,23 @@
                 }
             }
         });
-        setListAdapter(adapter);
+        mListView.setAdapter(adapter);
+
+        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
+                Activity activity = getActivity();
+                if (activity instanceof OnPrinterSelectedListener) {
+                    ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
+                } else {
+                    throw new IllegalStateException("the host activity must implement"
+                            + " OnPrinterSelectedListener");
+                }
+            }
+        });
+
+        return content;
     }
 
     @Override
@@ -132,7 +156,7 @@
 
             @Override
             public boolean onQueryTextChange(String searchString) {
-                ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString);
+                ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString);
                 return true;
             }
         });
@@ -168,15 +192,11 @@
     }
 
     @Override
-    public void onListItemClick(ListView list, View view, int position, long id) {
-        PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
-        Activity activity = getActivity();
-        if (activity instanceof OnPrinterSelectedListener) {
-            ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
-        } else {
-            throw new IllegalStateException("the host activity must implement"
-                    + " OnPrinterSelectedListener");
+    public void onPause() {
+        if (mAnnounceFilterResult != null) {
+            mAnnounceFilterResult.remove();
         }
+        super.onPause();
     }
 
     @Override
@@ -251,9 +271,9 @@
     }
 
     public void updateEmptyView(DestinationAdapter adapter) {
-        if (getListView().getEmptyView() == null) {
+        if (mListView.getEmptyView() == null) {
             View emptyView = getActivity().findViewById(R.id.empty_print_state);
-            getListView().setEmptyView(emptyView);
+            mListView.setEmptyView(emptyView);
         }
         TextView titleView = (TextView) getActivity().findViewById(R.id.title);
         View progressBar = getActivity().findViewById(R.id.progress_bar);
@@ -266,11 +286,13 @@
         }
     }
 
-    private void announceSearchResult() {
-        if (mAnnounceFilterResult == null) {
-            mAnnounceFilterResult = new AnnounceFilterResult();
+    private void announceSearchResultIfNeeded() {
+        if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+            if (mAnnounceFilterResult == null) {
+                mAnnounceFilterResult = new AnnounceFilterResult();
+            }
+            mAnnounceFilterResult.post();
         }
-        mAnnounceFilterResult.post();
     }
 
     public static class AddPrinterAlertDialogFragment extends DialogFragment {
@@ -397,7 +419,7 @@
                         resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
                     }
                     if (resultCountChanged) {
-                        announceSearchResult();
+                        announceSearchResultIfNeeded();
                     }
                     notifyDataSetChanged();
                 }
@@ -439,7 +461,7 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             if (convertView == null) {
                 convertView = getActivity().getLayoutInflater().inflate(
-                        R.layout.printer_dropdown_item, parent, false);
+                        R.layout.printer_list_item, parent, false);
             }
 
             convertView.setEnabled(isEnabled(position));
@@ -528,16 +550,16 @@
 
         public void post() {
             remove();
-            getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
+            mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
         }
 
         public void remove() {
-            getListView().removeCallbacks(this);
+            mListView.removeCallbacks(this);
         }
 
         @Override
         public void run() {
-            final int count = getListView().getAdapter().getCount();
+            final int count = mListView.getAdapter().getCount();
             final String text;
             if (count <= 0) {
                 text = getString(R.string.print_no_printers);
@@ -545,7 +567,7 @@
                 text = getActivity().getResources().getQuantityString(
                     R.plurals.print_search_result_count_utterance, count, count);
             }
-            getListView().announceForAccessibility(text);
+            mListView.announceForAccessibility(text);
         }
     }
 }
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index ab2feb9..783aa03 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -8,7 +8,7 @@
                  android:process="system"
                  android:backupAgent="SettingsBackupAgent"
                  android:killAfterRestore="false"
-                 android:icon="@drawable/ic_launcher_settings">
+                 android:icon="@mipmap/ic_launcher_settings">
                  
     <!-- todo add: android:neverEncrypt="true" -->
 
diff --git a/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png
deleted file mode 100644
index 8a5a2f7..0000000
--- a/packages/SettingsProvider/res/drawable-hdpi/ic_launcher_settings.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png
deleted file mode 100644
index 803439f..0000000
--- a/packages/SettingsProvider/res/drawable-mdpi/ic_launcher_settings.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png
deleted file mode 100644
index ec3c8ea..0000000
--- a/packages/SettingsProvider/res/drawable-xhdpi/ic_launcher_settings.png
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..a8ccc89
--- /dev/null
+++ b/packages/SettingsProvider/res/mipmap-hdpi/ic_launcher_settings.png
Binary files differ
diff --git a/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..69709a8
--- /dev/null
+++ b/packages/SettingsProvider/res/mipmap-mdpi/ic_launcher_settings.png
Binary files differ
diff --git a/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..c3adce61
--- /dev/null
+++ b/packages/SettingsProvider/res/mipmap-xhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..52fe978
--- /dev/null
+++ b/packages/SettingsProvider/res/mipmap-xxhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png b/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..6b92795
--- /dev/null
+++ b/packages/SettingsProvider/res/mipmap-xxxhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index bc02b0d..158227f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -47,6 +47,7 @@
 import android.os.DropBoxManager;
 import android.os.FileObserver;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -62,6 +63,8 @@
     private static final String TAG = "SettingsProvider";
     private static final boolean LOCAL_LOGV = false;
 
+    private static final boolean USER_CHECK_THROWS = true;
+
     private static final String TABLE_SYSTEM = "system";
     private static final String TABLE_SECURE = "secure";
     private static final String TABLE_GLOBAL = "global";
@@ -522,6 +525,14 @@
 
     // Lazy initialize the database helper and caches for this user, if necessary
     private DatabaseHelper getOrEstablishDatabase(int callingUser) {
+        if (callingUser >= Process.SYSTEM_UID) {
+            if (USER_CHECK_THROWS) {
+                throw new IllegalArgumentException("Uid rather than user handle: " + callingUser);
+            } else {
+                Slog.wtf(TAG, "establish db for uid rather than user: " + callingUser);
+            }
+        }
+
         long oldId = Binder.clearCallingIdentity();
         try {
             DatabaseHelper dbHelper = mOpenHelpers.get(callingUser);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 260a3be..09ac2da 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -200,7 +200,8 @@
             android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
             android:hardwareAccelerated="true"
             android:launchMode="singleInstance"
-            android:configChanges="orientation|screenSize"
+            android:screenOrientation="locked"
+            android:process=":sweetsweetdesserts"
             android:excludeFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -215,6 +216,7 @@
             android:exported="true"
             android:label="@string/dessert_case"
             android:enabled="false"
+            android:process=":sweetsweetdesserts"
             >
             <intent-filter>
                 <action android:name="android.service.dreams.DreamService" />
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_circle.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_circle.png
deleted file mode 100644
index f82a037..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_circle.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_hour.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_hour.png
deleted file mode 100644
index ed6c0db..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_hour.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_minute.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_minute.png
deleted file mode 100644
index e28c06a..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_clock_minute.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_1.png
index e20fdf7..2e3e486 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_2.png
index 79c9099..04e8220 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_3.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_3.png
index 8cb1cec..f8ed8f0 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_signal_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_0.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_0.png
index 6aa522b..c416fc3 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_0.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_1.png
index 2afa621..93b45b4 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_2.png
index c873c55..21b2c61 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_3.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_3.png
index e3e0e8d..cd96ae0 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_4.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_4.png
index 2762df5..43bfe3a 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_4.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_full_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_not_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_not_connected.png
index c9e0a7c..c416fc3 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_not_connected.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_wifi_not_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/nav_background.9.png b/packages/SystemUI/res/drawable-hdpi/nav_background.9.png
index db36d2b..a09e654 100644
--- a/packages/SystemUI/res/drawable-hdpi/nav_background.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/nav_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png
index 9befc34..d43d1dc 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_0.png b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_0.png
index d924756..49f9d8d 100644
--- a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_0.png b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_0.png
index 8177ef9..49f9d8d 100644
--- a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_2.png
index ec4b0a6..050dde5 100644
--- a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_0.png b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_0.png
index cdf76e2..ad699e2 100644
--- a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_0.png b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_0.png
index d41a2c7..ad699e2 100644
--- a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_2.png
index 2f21063..55e7125 100644
--- a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_0.png b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_0.png
index 10c6905..e50be70 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_0.png b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_0.png
index ef8c677..e50be70 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_2.png
index 8d3dede..5a69da2 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_0.png b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_0.png
index 5c378ef..5298d41 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_0.png b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_0.png
index 6db5d0d..a9c0849 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_0.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_2.png
index 4faff9b..3a1678f 100644
--- a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_circle.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_circle.png
deleted file mode 100644
index 3073986..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_circle.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_hour.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_hour.png
deleted file mode 100644
index 2a0bc59..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_hour.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_minute.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_minute.png
deleted file mode 100644
index 9b1cc58..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_clock_minute.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_1.png
index 52faded..1d063d7 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_1.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_2.png
index bcf825d..b8551ac 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_3.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_3.png
index f9de3ef..25e5586 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_3.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_signal_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_0.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_0.png
index 42210a6..b177999 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_0.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_1.png
index 092ddbb..b3318bc 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_1.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_2.png
index e10a1da..18f5307 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_2.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_3.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_3.png
index bd235ae..6d08001c 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_3.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_4.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_4.png
index 5873b37..ef3023e 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_4.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_full_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_not_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_not_connected.png
index fdf34bf..b177999 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_not_connected.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_wifi_not_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/nav_background.9.png b/packages/SystemUI/res/drawable-mdpi/nav_background.9.png
index 45e6e8f..aa74153 100644
--- a/packages/SystemUI/res/drawable-mdpi/nav_background.9.png
+++ b/packages/SystemUI/res/drawable-mdpi/nav_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png
index 2e24f6f..61d7511 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_circle.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_circle.png
deleted file mode 100644
index 72c587d..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_circle.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_hour.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_hour.png
deleted file mode 100644
index d18dbd9..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_hour.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_minute.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_minute.png
deleted file mode 100644
index 31230af..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_clock_minute.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_1.png
index 58317e3..d6af953d 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_1.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_2.png
index 57b8039..a702239 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_3.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_3.png
index 0cb099c..33e0310 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_3.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_signal_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_0.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_0.png
index 5599069..2afe504 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_0.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_1.png
index 0702c31..6df8484 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_1.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_2.png
index 6693090..d7915c6 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_2.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_3.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_3.png
index ded4c67..c3773d2 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_3.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_4.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_4.png
index c2e0da9..263d697 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_4.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_full_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_not_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_not_connected.png
index 2344349..2afe504 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_not_connected.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_wifi_not_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/nav_background.9.png b/packages/SystemUI/res/drawable-xhdpi/nav_background.9.png
index 152e4ac..3b52195 100644
--- a/packages/SystemUI/res/drawable-xhdpi/nav_background.9.png
+++ b/packages/SystemUI/res/drawable-xhdpi/nav_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png
index a7f0017..192d3f7 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_circle.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_circle.png
deleted file mode 100644
index 849d547..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_circle.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_hour.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_hour.png
deleted file mode 100644
index 57dd8a6..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_hour.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_minute.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_minute.png
deleted file mode 100644
index a9b8ba5..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_clock_minute.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_1.png
index edc44ab..d8556b2 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_1.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_2.png
index 5bd9b76..b913f6e 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_2.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_3.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_3.png
index 16196a03..7a1de13 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_3.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_signal_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_0.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_0.png
index f1d9f21..1951654 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_0.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_1.png
index dbae28c..f60d8a5 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_1.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_2.png
index 06e8d5b..ac88239 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_2.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_3.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_3.png
index bff132b..4a3c770 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_3.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_4.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_4.png
index 636bd702..b7e7d6f 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_4.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_full_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_not_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_not_connected.png
index 9c5a207..89c8fc1 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_not_connected.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_wifi_not_connected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/nav_background.9.png b/packages/SystemUI/res/drawable-xxhdpi/nav_background.9.png
index cce2e062..b35183c 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/nav_background.9.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/nav_background.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_device_access_location_found.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_device_access_location_found.png
index ad34d49..1e5f15f 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_device_access_location_found.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_device_access_location_found.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_time.xml b/packages/SystemUI/res/layout/quick_settings_tile_time.xml
deleted file mode 100644
index 910e1f6..0000000
--- a/packages/SystemUI/res/layout/quick_settings_tile_time.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:orientation="vertical">
-    <AnalogClock
-        android:id="@+id/analog_clock"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_gravity="center"
-        android:layout_marginBottom="10dp"
-        android:dial="@drawable/ic_qs_clock_circle"
-        android:hand_hour="@drawable/ic_qs_clock_hour"
-        android:hand_minute="@drawable/ic_qs_clock_minute"
-        />
-    <com.android.systemui.statusbar.policy.DateView
-        android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:gravity="center"
-        />
-</LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ad73f28..d8e738e 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -199,8 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Draadlose aansig"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Helderheid"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OUTO"</string>
-    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Kleur-inversiemodus"</string>
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Kleur-omkeringmodus"</string>
     <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Verbeterde kontrasmodus"</string>
-    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Kleurkorreksiemodus"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Kleurregstellingmodus"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Netwerk word\ndalk gemonitor"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 093a1d8..551b9f2 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"عرض شاشة لاسلكي"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"السطوع"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"تلقائي"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"وضع انعكاس اللون"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"وضع التباين المحسن"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"وضع تصحيح الألوان"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"قد تكون الشبكة\nخاضعة للرقابة"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index c0eb80d..f2f76d7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -61,7 +61,7 @@
     <string name="usb_debugging_always" msgid="303335496705863070">"Dóna sempre permís des d\'aquest equip"</string>
     <string name="compat_mode_on" msgid="6623839244840638213">"Zoom per omplir pantalla"</string>
     <string name="compat_mode_off" msgid="4434467572461327898">"Estira per omplir pant."</string>
-    <string name="screenshot_saving_ticker" msgid="7403652894056693515">"Desant captura de pantalla..."</string>
+    <string name="screenshot_saving_ticker" msgid="7403652894056693515">"S\'està desant captura de pantalla..."</string>
     <string name="screenshot_saving_title" msgid="8242282144535555697">"S\'està desant la captura de pantalla..."</string>
     <string name="screenshot_saving_text" msgid="2419718443411738818">"La captura de pantalla s\'ha desat."</string>
     <string name="screenshot_saved_title" msgid="6461865960961414961">"S\'ha fet una captura de pantalla."</string>
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Pantalla sense fil"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Brillantor"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMÀTICA"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Mode d\'inversió de color"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Mode de contrast millorat"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Mode de correcció de color"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"És possible que la xarxa\nestigui controlada"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 722e7c6..1b5bbcb 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"نمایش بدون سیم"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"روشنایی"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"خودکار"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"حالت وارونگی رنگ"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"حالت کنتراست بهبودیافته"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"حالت تصحیح رنگ"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"ممکن است شبکه\nتحت نظارت باشد"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 8bd2918..6365bd9 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Affichage sans fil"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Luminosité"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATIQUE"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Mode d\'inversion des couleurs"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Mode d\'accentuation du contraste"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Mode de correction des couleurs"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Le réseau peut\nêtre surveillé."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index c0d58ae..75ca849 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -22,7 +22,7 @@
     <string name="app_label" msgid="7164937344850004466">"सिस्‍टम UI"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"साफ़ करें"</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_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">
@@ -75,7 +75,7 @@
     <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_search_light" msgid="1103867596330271848">"खोजें"</string>
     <string name="accessibility_camera_button" msgid="8064671582820358152">"कैमरा"</string>
     <string name="accessibility_ime_switch_button" msgid="5032926134740456424">"इनपुट पद्धति‍ बटन स्विच करें."</string>
@@ -146,7 +146,7 @@
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"सूचना खारिज की गई."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"सूचना शेड."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"त्वरित सेटिंग."</string>
-    <string name="accessibility_desc_recent_apps" msgid="9014032916410590027">"हाल ही के एप्‍स."</string>
+    <string name="accessibility_desc_recent_apps" msgid="9014032916410590027">"हाल ही के ऐप्स."</string>
     <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"उपयोगकर्ता <xliff:g id="USER">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_wifi" msgid="6099781031669728709">"<xliff:g id="SIGNAL">%1$s</xliff:g>. <xliff:g id="NETWORK">%2$s</xliff:g>"</string>
     <string name="accessibility_quick_settings_mobile" msgid="4876806564086241341">"मोबाइल <xliff:g id="SIGNAL">%1$s</xliff:g>. <xliff:g id="TYPE">%2$s</xliff:g>. <xliff:g id="NETWORK">%3$s</xliff:g>."</string>
@@ -166,7 +166,7 @@
     <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS द्वारा सेट किया गया स्‍थान"</string>
     <string name="accessibility_location_active" msgid="2427290146138169014">"स्थान अनुरोध सक्रिय"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"सभी सूचनाएं साफ़ करें."</string>
-    <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"एप्‍स जानकारी"</string>
+    <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"ऐप्स जानकारी"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"स्‍क्रीन स्‍वचालित रूप से घूमेगी."</string>
     <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"स्‍क्रीन लैंडस्केप अभिविन्यास में लॉक है."</string>
     <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"स्‍क्रीन पोर्ट्रेट अभिविन्‍यास में लॉक है."</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index 7f788da..a7ee881 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"უსადენო ეკრანი"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"განათება"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"ავტომატურად"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"ფერთა ინვერსიის რეჟიმი"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"გაუმჯობესებული კონტრასტის რეჟიმი"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"ფერთა კორექციის რეჟიმი"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"შესაძლოა ქსელზე\nმონიტორინგი ხორციელდებოდეს"</string>
 </resources>
diff --git a/core/res/res/values-mcc202/config.xml b/packages/SystemUI/res/values-mcc262-mnc08/config.xml
similarity index 65%
copy from core/res/res/values-mcc202/config.xml
copy to packages/SystemUI/res/values-mcc262-mnc08/config.xml
index 8d6d3b1..7b7b8f3 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/packages/SystemUI/res/values-mcc262-mnc08/config.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@
 */
 -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Whether or not the RSSI tile is capitalized or not. -->
+    <bool name="quick_settings_rssi_tile_capitalization">false</bool>
 </resources>
+
diff --git a/core/res/res/values-mcc202/config.xml b/packages/SystemUI/res/values-mcc262-mnc11/config.xml
similarity index 65%
copy from core/res/res/values-mcc202/config.xml
copy to packages/SystemUI/res/values-mcc262-mnc11/config.xml
index 8d6d3b1..7b7b8f3 100644
--- a/core/res/res/values-mcc202/config.xml
+++ b/packages/SystemUI/res/values-mcc262-mnc11/config.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2012, The Android Open Source Project
+** Copyright 2013, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@
 */
 -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
-    <bool name="config_safe_media_volume_enabled">true</bool>
-
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Whether or not the RSSI tile is capitalized or not. -->
+    <bool name="quick_settings_rssi_tile_capitalization">false</bool>
 </resources>
+
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 4693949..bbfd111 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Wyświetlacz bezprzewodowy"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Jasność"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATYCZNA"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Tryb odwrócenia kolorów"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Tryb zwiększonego kontrastu"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Tryb korekcji kolorów"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Sieć może być\nmonitorowana"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 6e45f46..2d09928 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -203,11 +203,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Wi-Fi-монитор"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Яркость"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"АВТОНАСТРОЙКА"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Инверсия цвета"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Контрастность"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Коррекция цвета"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Действия в сети\nмогут отслеживаться"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 4ac4f16..4e4c3f8 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Bezdrôtový displej"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Jas"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATICKY"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Režim prevrátenia farieb"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Režim zvýšeného kontrastu"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Režim korekcie farieb"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Sieť môže byť\nmonitorovaná"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index f926440..ff88288 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Trådlös skärm"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Ljusstyrka"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Färginverteringsläge"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Kontrastförbättringsläge"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Färgkorrigeringsläge"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Nätverket kan\nvara övervakat"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index d46d433..29614e8 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -197,11 +197,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Uonyeshaji Pasiwaya"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Ung\'avu"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"KIOTOMATIKI"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Hali ya ugeuzaji kinyume wa rangi"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Hali ya utofautishaji ulioboreshwa"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Hali ya kusahihisha rangi"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Huenda mtandao\nunafuatiliwa"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index a105b35..cee2b5a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -199,11 +199,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"Kablosuz Ekran"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Parlaklık"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"OTOMATİK"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"Renk ters çevirme modu"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"Geliştirilmiş kontrast modu"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Renk düzeltme modu"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"Ağ izleniyor\nolabilir"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 0ba1bb0..f30ac2a 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -147,7 +147,7 @@
     <string name="accessibility_recents_item_dismissed" msgid="6803574935084867070">"已删除<xliff:g id="APP">%s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"已关闭通知。"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"通知栏。"</string>
-    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"快速设置。"</string>
+    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"快捷设置。"</string>
     <string name="accessibility_desc_recent_apps" msgid="9014032916410590027">"最近使用的应用。"</string>
     <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"用户:<xliff:g id="USER">%s</xliff:g>。"</string>
     <string name="accessibility_quick_settings_wifi" msgid="6099781031669728709">"<xliff:g id="SIGNAL">%1$s</xliff:g>,<xliff:g id="NETWORK">%2$s</xliff:g>"</string>
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"无线显示"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"亮度"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自动"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"颜色反转模式"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"增强对比度模式"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"颜色校正模式"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"网络可能会\n受到监控"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 86972e8..10f52c5 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"無線顯示"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"亮度"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自動"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"色彩反轉模式"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"增強對比模式"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"色彩校準模式"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"網絡可能會\n受到監控"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index c916ec6..aa8a751 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -201,11 +201,8 @@
     <string name="quick_settings_wifi_display_no_connection_label" msgid="2355298740765736918">"無線螢幕分享"</string>
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"亮度"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"自動"</string>
-    <!-- no translation found for quick_settings_inversion_label (1666358784283020762) -->
-    <skip />
-    <!-- no translation found for quick_settings_contrast_label (3319507551689108692) -->
-    <skip />
-    <!-- no translation found for quick_settings_color_space_label (853443689745584770) -->
-    <skip />
+    <string name="quick_settings_inversion_label" msgid="1666358784283020762">"彩色反轉模式"</string>
+    <string name="quick_settings_contrast_label" msgid="3319507551689108692">"增強對比模式"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"色彩校正模式"</string>
     <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"網路可能\n受到監控"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 73786f0..81d1289 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -496,11 +496,11 @@
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_auto_brightness_label">AUTO</string>
-    <!-- QuickSettings: Color inversion mode [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_inversion_label">Color inversion mode</string>
-    <!-- QuickSettings: Enhanced contrast mode [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Label for the toggle that controls whether display contrast enhancement is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_contrast_label">Enhanced contrast mode</string>
-    <!-- QuickSettings: Color correction mode [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_color_space_label">Color correction mode</string>
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 150f132..b6e03e1 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -69,8 +69,10 @@
     private final Rect mBoltFrame = new Rect();
 
     private class BatteryTracker extends BroadcastReceiver {
+        public static final int UNKNOWN_LEVEL = -1;
+
         // current battery status
-        int level;
+        int level = UNKNOWN_LEVEL;
         String percentStr;
         int plugType;
         boolean plugged;
@@ -148,7 +150,11 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(ACTION_LEVEL_TEST);
-        getContext().registerReceiver(mTracker, filter);
+        final Intent sticky = getContext().registerReceiver(mTracker, filter);
+        if (sticky != null) {
+            // preload the battery level
+            mTracker.onReceive(getContext(), sticky);
+        }
     }
 
     @Override
@@ -256,6 +262,9 @@
     public void draw(Canvas c) {
         BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
         final int level = tracker.level;
+
+        if (level == BatteryTracker.UNKNOWN_LEVEL) return;
+
         float drawFrac = (float) level / 100f;
         final int pt = getPaddingTop();
         final int pl = getPaddingLeft();
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
index 99c59d5..90de65e 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -96,6 +96,13 @@
             1f,  0f,  0f,  0f, 0f
     };
 
+    private static final float[] ALPHA_MASK = {
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  0f, 255f,
+            0f,  0f,  0f,  1f, 0f
+    };
+
     private static final float[] WHITE_MASK = {
             0f,  0f,  0f,  0f, 255f,
             0f,  0f,  0f,  0f, 255f,
@@ -162,8 +169,8 @@
         for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) {
             for (int resid : list) {
                 final BitmapDrawable d = new BitmapDrawable(res,
-                        BitmapFactory.decodeResource(res, resid, opts));
-                d.setColorFilter(new ColorMatrixColorFilter(MASK));
+                        convertToAlphaMask(BitmapFactory.decodeResource(res, resid, opts)));
+                d.setColorFilter(new ColorMatrixColorFilter(ALPHA_MASK));
                 d.setBounds(0, 0, mCellSize, mCellSize);
                 mDrawables.append(resid, d);
             }
@@ -171,6 +178,15 @@
         if (DEBUG) setWillNotDraw(false);
     }
 
+    private static Bitmap convertToAlphaMask(Bitmap b) {
+        Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
+        Canvas c = new Canvas(a);
+        Paint pt = new Paint();
+        pt.setColorFilter(new ColorMatrixColorFilter(MASK));
+        c.drawBitmap(b, 0.0f, 0.0f, pt);
+        return a;
+    }
+
     public void start() {
         if (!mStarted) {
             mStarted = true;
@@ -273,9 +289,9 @@
 
             final float which = frand();
             final Drawable d;
-            if (which < 0.001f) {
+            if (which < 0.0005f) {
                 d = mDrawables.get(pick(XXRARE_PASTRIES));
-            } else if (which < 0.01f) {
+            } else if (which < 0.005f) {
                 d = mDrawables.get(pick(XRARE_PASTRIES));
             } else if (which < 0.5f) {
                 d = mDrawables.get(pick(RARE_PASTRIES));
@@ -288,8 +304,7 @@
                 v.getOverlay().add(d);
             }
 
-            final Paint paint = new Paint();
-            v.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
             lp.width = lp.height = mCellSize;
             addView(v, lp);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 6fa863d..c7f4828 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -326,6 +326,7 @@
                             ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
                             dw + ", " + dh);
                 }
+                mWallpaperManager.forgetLoadedWallpaper();
                 updateWallpaperLocked();
                 if (mBackground == null) {
                     if (DEBUG) {
@@ -470,7 +471,7 @@
 
             checkGlError();
 
-            if (w < 0 || h < 0) {
+            if (w > 0 || h > 0) {
                 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
                 glClear(GL_COLOR_BUFFER_BIT);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 932fe20..6a2bc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -570,12 +570,14 @@
                  if (DEBUG) Log.d(TAG, "opening search panel");
                  if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
                      mSearchPanelView.show(true, true);
+                     onShowSearchPanel();
                  }
                  break;
              case MSG_CLOSE_SEARCH_PANEL:
                  if (DEBUG) Log.d(TAG, "closing search panel");
                  if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
                      mSearchPanelView.show(false, true);
+                     onHideSearchPanel();
                  }
                  break;
             }
@@ -607,6 +609,12 @@
     protected void workAroundBadLayerDrawableOpacity(View v) {
     }
 
+    protected void onHideSearchPanel() {
+    }
+
+    protected void onShowSearchPanel() {
+    }
+
     public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
         int minHeight =
                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 6e53363..d1c4109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -37,10 +37,10 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
@@ -89,26 +89,31 @@
     // used to disable the camera icon in navbar when disabled by DPM
     private boolean mCameraDisabledByDpm;
 
+    // simplified click handler to be used when device is in accessibility mode
+    private final OnClickListener mAccessibilityClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (v.getId() == R.id.camera_button) {
+                KeyguardTouchDelegate.getInstance(getContext()).launchCamera();
+            } else if (v.getId() == R.id.search_light) {
+                KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
+            }
+        }
+    };
+
     private final OnTouchListener mCameraTouchListener = new OnTouchListener() {
         @Override
         public boolean onTouch(View cameraButtonView, MotionEvent event) {
-            View searchLight = getSearchLight();
             switch (event.getAction()) {
                 case MotionEvent.ACTION_DOWN:
                     // disable search gesture while interacting with camera
                     mDelegateHelper.setDisabled(true);
-                    cameraButtonView.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
-                    if (searchLight != null) {
-                        searchLight.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
-                    }
+                    transitionCameraAndSearchButtonAlpha(0.0f);
                     break;
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
                     mDelegateHelper.setDisabled(false);
-                    cameraButtonView.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
-                    if (searchLight != null) {
-                        searchLight.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION);
-                    }
+                    transitionCameraAndSearchButtonAlpha(1.0f);
                     break;
             }
             return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event);
@@ -158,6 +163,17 @@
         watchForDevicePolicyChanges();
     }
 
+    protected void transitionCameraAndSearchButtonAlpha(float alpha) {
+        View cameraButtonView = getCameraButton();
+        if (cameraButtonView != null) {
+            cameraButtonView.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION);
+        }
+        View searchLight = getSearchLight();
+        if (searchLight != null) {
+            searchLight.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION);
+        }
+    }
+
     private void watchForDevicePolicyChanges() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
@@ -388,44 +404,49 @@
 
         mCurrentView = mRotatedViews[Surface.ROTATION_0];
 
-
-        final AccessibilityManager accessibilityManager =
-                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        if (accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled()) {
-            // In accessibility mode, we add a simple click handler since swipe is tough to
-            // trigger near screen edges.
-            View camera = getCameraButton();
-            View searchLight = getSearchLight();
-            if (camera != null || searchLight != null) {
-                OnClickListener listener = new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        launchForAccessibilityClick(v);
-                    }
-                };
-                if (camera != null) {
-                    camera.setOnClickListener(listener);
-                }
-                if (searchLight != null) {
-                    searchLight.setOnClickListener(listener);
-                }
-            }
-        } else {
-            // Add a touch handler for camera icon for all view orientations.
-            for (int i = 0; i < mRotatedViews.length; i++) {
-                View cameraButton = mRotatedViews[i].findViewById(R.id.camera_button);
-                if (cameraButton != null) {
-                    cameraButton.setOnTouchListener(mCameraTouchListener);
-                }
-            }
-        }
+        watchForAccessibilityChanges();
     }
 
-    protected void launchForAccessibilityClick(View v) {
-        if (v == getCameraButton()) {
-            KeyguardTouchDelegate.getInstance(getContext()).launchCamera();
-        } else if (v == getSearchLight()) {
-            KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
+    private void watchForAccessibilityChanges() {
+        final AccessibilityManager am =
+                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        // Set the initial state
+        enableAccessibility(am.isTouchExplorationEnabled());
+
+        // Watch for changes
+        am.addTouchExplorationStateChangeListener(new TouchExplorationStateChangeListener() {
+            @Override
+            public void onTouchExplorationStateChanged(boolean enabled) {
+                enableAccessibility(enabled);
+            }
+        });
+    }
+
+    private void enableAccessibility(boolean touchEnabled) {
+        Log.v(TAG, "touchEnabled:"  + touchEnabled);
+
+        // Add a touch handler or accessibility click listener for camera and search buttons
+        // for all view orientations.
+        final OnClickListener onClickListener = touchEnabled ? mAccessibilityClickListener : null;
+        final OnTouchListener onTouchListener = touchEnabled ? null : mCameraTouchListener;
+        boolean hasCamera = false;
+        for (int i = 0; i < mRotatedViews.length; i++) {
+            final View cameraButton = mRotatedViews[i].findViewById(R.id.camera_button);
+            final View searchLight = mRotatedViews[i].findViewById(R.id.search_light);
+            if (cameraButton != null) {
+                hasCamera = true;
+                cameraButton.setOnTouchListener(onTouchListener);
+                cameraButton.setOnClickListener(onClickListener);
+            }
+            if (searchLight != null) {
+                searchLight.setOnClickListener(onClickListener);
+            }
+        }
+        if (hasCamera) {
+            // Warm up KeyguardTouchDelegate so it's ready by the time the camera button is touched.
+            // This will connect to KeyguardService so that touch events are processed.
+            KeyguardTouchDelegate.getInstance(mContext);
         }
     }
 
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 c02a99b..925179d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -343,7 +343,7 @@
     public void start() {
         mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                 .getDefaultDisplay();
-        mDisplay.getSize(mCurrentDisplaySize);
+        updateDisplaySize();
 
         super.start(); // calls createAndAddWindows()
 
@@ -647,6 +647,20 @@
     }
 
     @Override
+    protected void onShowSearchPanel() {
+        if (mNavigationBarView != null) {
+            mNavigationBarView.transitionCameraAndSearchButtonAlpha(0.0f);
+        }
+    }
+
+    @Override
+    protected void onHideSearchPanel() {
+        if (mNavigationBarView != null) {
+            mNavigationBarView.transitionCameraAndSearchButtonAlpha(1.0f);
+        }
+    }
+
+    @Override
     protected View getStatusBarView() {
         return mStatusBarView;
     }
@@ -1914,8 +1928,12 @@
 
     private void checkBarModes() {
         if (mDemoMode) return;
-        checkBarMode((mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0 ? MODE_OPAQUE
-                : mStatusBarMode, mStatusBarWindowState, mStatusBarView.getBarTransitions());
+        int sbMode = mStatusBarMode;
+        if (panelsEnabled() && (mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0) {
+            // if panels are expandable, force the status bar opaque on any interaction
+            sbMode = MODE_OPAQUE;
+        }
+        checkBarMode(sbMode, mStatusBarWindowState, mStatusBarView.getBarTransitions());
         if (mNavigationBarView != null) {
             checkBarMode(mNavigationBarMode,
                     mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
@@ -1923,10 +1941,8 @@
     }
 
     private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
-        final boolean imeVisible = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0;
-        final int finalMode = imeVisible ? MODE_OPAQUE : mode;
         final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN;
-        transitions.transitionTo(finalMode, anim);
+        transitions.transitionTo(mode, anim);
     }
 
     private final Runnable mCheckBarModes = new Runnable() {
@@ -2292,6 +2308,7 @@
     // called by makeStatusbar and also by PhoneStatusBarView
     void updateDisplaySize() {
         mDisplay.getMetrics(mDisplayMetrics);
+        mDisplay.getSize(mCurrentDisplaySize);
         if (DEBUG_GESTURES) {
             mGestureRec.tag("display",
                     String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -2463,7 +2480,7 @@
         if (DEBUG) {
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
         }
-        mDisplay.getSize(mCurrentDisplaySize);
+        updateDisplaySize(); // populates mDisplayMetrics
 
         updateResources();
         repositionNavigationBar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index a3cadf2..855e937 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -360,26 +360,6 @@
         parent.addView(brightnessTile);
         mDynamicSpannedTiles.add(brightnessTile);
 
-        // Time tile
-        /*
-        QuickSettingsTileView timeTile = (QuickSettingsTileView)
-                inflater.inflate(R.layout.quick_settings_tile, parent, false);
-        timeTile.setContent(R.layout.quick_settings_tile_time, inflater);
-        timeTile.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                // Quick. Clock. Quick. Clock. Quick. Clock.
-                startSettingsActivity(Intent.ACTION_QUICK_CLOCK);
-            }
-        });
-        mModel.addTimeTile(timeTile, new QuickSettingsModel.RefreshCallback() {
-            @Override
-            public void refreshView(QuickSettingsTileView view, State alarmState) {}
-        });
-        parent.addView(timeTile);
-        mDynamicSpannedTiles.add(timeTile);
-        */
-
         // Settings tile
         final QuickSettingsBasicTile settingsTile = new QuickSettingsBasicTile(mContext);
         settingsTile.setImageResource(R.drawable.ic_qs_settings);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 531126d..fa97a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -35,6 +35,7 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -431,6 +432,8 @@
         refreshBluetoothTile();
         refreshBrightnessTile();
         refreshRotationLockTile();
+        refreshRssiTile();
+        refreshLocationTile();
     }
 
     // Settings
@@ -624,6 +627,14 @@
         }
     }
 
+    void refreshRssiTile() {
+        if (mRSSITile != null) {
+            // We reinflate the original view due to potential styling changes that may have
+            // taken place due to a configuration change.
+            mRSSITile.reinflateContent(LayoutInflater.from(mContext));
+        }
+    }
+
     // Bluetooth
     void addBluetoothTile(QuickSettingsTileView view, RefreshCallback cb) {
         mBluetoothTile = view;
@@ -695,6 +706,12 @@
         mLocationCallback.refreshView(mLocationTile, mLocationState);
     }
 
+    void refreshLocationTile() {
+        if (mLocationTile != null) {
+            onLocationSettingsChanged(mLocationState.enabled);
+        }
+    }
+
     @Override
     public void onLocationSettingsChanged(boolean locationEnabled) {
         int textResId = locationEnabled ? R.string.quick_settings_location_label
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
index 9cff242..3d520f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -26,13 +27,16 @@
  *
  */
 class QuickSettingsTileView extends FrameLayout {
+    private static final String TAG = "QuickSettingsTileView";
 
+    private int mContentLayoutId;
     private int mColSpan;
     private int mRowSpan;
 
     public QuickSettingsTileView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        mContentLayoutId = -1;
         mColSpan = 1;
         mRowSpan = 1;
     }
@@ -46,9 +50,19 @@
     }
 
     void setContent(int layoutId, LayoutInflater inflater) {
+        mContentLayoutId = layoutId;
         inflater.inflate(layoutId, this);
     }
 
+    void reinflateContent(LayoutInflater inflater) {
+        if (mContentLayoutId != -1) {
+            removeAllViews();
+            setContent(mContentLayoutId, inflater);
+        } else {
+            Log.e(TAG, "Not reinflating content: No layoutId set");
+        }
+    }
+
     @Override
     public void setVisibility(int vis) {
         if (QuickSettings.DEBUG_GONE_TILES) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 16e2e07..b7f3cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -50,7 +50,8 @@
                     || Intent.ACTION_TIME_CHANGED.equals(action)
                     || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
                     || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
-                if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+                if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+                        || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
                     // need to get a fresh date format
                     mDateFormat = null;
                 }
diff --git a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageView.java b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageView.java
index 36cb438..af4199c 100644
--- a/packages/WallpaperCropper/src/com/android/photos/views/TiledImageView.java
+++ b/packages/WallpaperCropper/src/com/android/photos/views/TiledImageView.java
@@ -63,7 +63,7 @@
         // Guarded by locks
         public float scale;
         public int centerX, centerY;
-        int rotation;
+        public int rotation;
         public TileSource source;
         Runnable isReadyCallback;
 
diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
index b4e715c..14f7c1d 100644
--- a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
+++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java
@@ -17,9 +17,11 @@
 package com.android.wallpapercropper;
 
 import android.content.Context;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.util.FloatMath;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
@@ -36,10 +38,18 @@
     private long mTouchDownTime;
     private float mFirstX, mFirstY;
     private float mLastX, mLastY;
+    private float mCenterX, mCenterY;
     private float mMinScale;
     private boolean mTouchEnabled = true;
     private RectF mTempEdges = new RectF();
+    private float[] mTempPoint = new float[] { 0, 0 };
+    private float[] mTempCoef = new float[] { 0, 0 };
+    private float[] mTempAdjustment = new float[] { 0, 0 };
+    private float[] mTempImageDims = new float[] { 0, 0 };
+    private float[] mTempRendererCenter = new float[] { 0, 0 };
     TouchCallback mTouchCallback;
+    Matrix mRotateMatrix;
+    Matrix mInverseRotateMatrix;
 
     public interface TouchCallback {
         void onTouchDown();
@@ -54,17 +64,43 @@
     public CropView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mScaleGestureDetector = new ScaleGestureDetector(context, this);
+        mRotateMatrix = new Matrix();
+        mInverseRotateMatrix = new Matrix();
+    }
+
+    private float[] getImageDims() {
+        final float imageWidth = mRenderer.source.getImageWidth();
+        final float imageHeight = mRenderer.source.getImageHeight();
+        float[] imageDims = mTempImageDims;
+        imageDims[0] = imageWidth;
+        imageDims[1] = imageHeight;
+        mRotateMatrix.mapPoints(imageDims);
+        imageDims[0] = Math.abs(imageDims[0]);
+        imageDims[1] = Math.abs(imageDims[1]);
+        return imageDims;
     }
 
     private void getEdgesHelper(RectF edgesOut) {
         final float width = getWidth();
         final float height = getHeight();
-        final float imageWidth = mRenderer.source.getImageWidth();
-        final float imageHeight = mRenderer.source.getImageHeight();
+        final float[] imageDims = getImageDims();
+        final float imageWidth = imageDims[0];
+        final float imageHeight = imageDims[1];
+
+        float initialCenterX = mRenderer.source.getImageWidth() / 2f;
+        float initialCenterY = mRenderer.source.getImageHeight() / 2f;
+
+        float[] rendererCenter = mTempRendererCenter;
+        rendererCenter[0] = mCenterX - initialCenterX;
+        rendererCenter[1] = mCenterY - initialCenterY;
+        mRotateMatrix.mapPoints(rendererCenter);
+        rendererCenter[0] += imageWidth / 2;
+        rendererCenter[1] += imageHeight / 2;
+
         final float scale = mRenderer.scale;
-        float centerX = (width / 2f - mRenderer.centerX + (imageWidth - width) / 2f)
+        float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
                 * scale + width / 2f;
-        float centerY = (height / 2f - mRenderer.centerY + (imageHeight - height) / 2f)
+        float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
                 * scale + height / 2f;
         float leftEdge = centerX - imageWidth / 2f * scale;
         float rightEdge = centerX + imageWidth / 2f * scale;
@@ -77,6 +113,10 @@
         edgesOut.bottom = bottomEdge;
     }
 
+    public int getImageRotation() {
+        return mRenderer.rotation;
+    }
+
     public RectF getCrop() {
         final RectF edges = mTempEdges;
         getEdgesHelper(edges);
@@ -96,6 +136,12 @@
 
     public void setTileSource(TileSource source, Runnable isReadyCallback) {
         super.setTileSource(source, isReadyCallback);
+        mCenterX = mRenderer.centerX;
+        mCenterY = mRenderer.centerY;
+        mRotateMatrix.reset();
+        mRotateMatrix.setRotate(mRenderer.rotation);
+        mInverseRotateMatrix.reset();
+        mInverseRotateMatrix.setRotate(-mRenderer.rotation);
         updateMinScale(getWidth(), getHeight(), source, true);
     }
 
@@ -115,8 +161,10 @@
                 mRenderer.scale = 1;
             }
             if (source != null) {
-                mMinScale = Math.max(w / (float) source.getImageWidth(),
-                        h / (float) source.getImageHeight());
+                final float[] imageDims = getImageDims();
+                final float imageWidth = imageDims[0];
+                final float imageHeight = imageDims[1];
+                mMinScale = Math.max(w / imageWidth, h / imageHeight);
                 mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
             }
         }
@@ -154,7 +202,13 @@
         final RectF edges = mTempEdges;
         getEdgesHelper(edges);
         final float scale = mRenderer.scale;
-        mRenderer.centerX += Math.ceil(edges.left / scale);
+        mCenterX += Math.ceil(edges.left / scale);
+        updateCenter();
+    }
+
+    private void updateCenter() {
+        mRenderer.centerX = Math.round(mCenterX);
+        mRenderer.centerY = Math.round(mCenterY);
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -200,7 +254,7 @@
             if (mTouchCallback != null) {
                 // only do this if it's a small movement
                 if (squaredDist < slop &&
-                    now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
+                        now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
                     mTouchCallback.onTap();
                 }
                 mTouchCallback.onTouchUp();
@@ -215,8 +269,13 @@
             mScaleGestureDetector.onTouchEvent(event);
             switch (action) {
                 case MotionEvent.ACTION_MOVE:
-                    mRenderer.centerX += (mLastX - x) / mRenderer.scale;
-                    mRenderer.centerY += (mLastY - y) / mRenderer.scale;
+                    float[] point = mTempPoint;
+                    point[0] = (mLastX - x) / mRenderer.scale;
+                    point[1] = (mLastY - y) / mRenderer.scale;
+                    mInverseRotateMatrix.mapPoints(point);
+                    mCenterX += point[0];
+                    mCenterY += point[1];
+                    updateCenter();
                     invalidate();
                     break;
             }
@@ -226,18 +285,32 @@
                 final RectF edges = mTempEdges;
                 getEdgesHelper(edges);
                 final float scale = mRenderer.scale;
+
+                float[] coef = mTempCoef;
+                coef[0] = 1;
+                coef[1] = 1;
+                mRotateMatrix.mapPoints(coef);
+                float[] adjustment = mTempAdjustment;
+                mTempAdjustment[0] = 0;
+                mTempAdjustment[1] = 0;
                 if (edges.left > 0) {
-                    mRenderer.centerX += Math.ceil(edges.left / scale);
-                }
-                if (edges.right < getWidth()) {
-                    mRenderer.centerX += (edges.right - getWidth()) / scale;
+                    adjustment[0] = edges.left / scale;
+                } else if (edges.right < getWidth()) {
+                    adjustment[0] = (edges.right - getWidth()) / scale;
                 }
                 if (edges.top > 0) {
-                    mRenderer.centerY += Math.ceil(edges.top / scale);
+                    adjustment[1] = FloatMath.ceil(edges.top / scale);
+                } else if (edges.bottom < getHeight()) {
+                    adjustment[1] = (edges.bottom - getHeight()) / scale;
                 }
-                if (edges.bottom < getHeight()) {
-                    mRenderer.centerY += (edges.bottom - getHeight()) / scale;
+                for (int dim = 0; dim <= 1; dim++) {
+                    if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
                 }
+
+                mInverseRotateMatrix.mapPoints(adjustment);
+                mCenterX += adjustment[0];
+                mCenterY += adjustment[1];
+                updateCenter();
             }
         }
 
diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
index 710e8e4..1209e56 100644
--- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
+++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java
@@ -37,12 +37,14 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.util.FloatMath;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
 
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
 import com.android.photos.BitmapRegionTileSource;
 
 import java.io.BufferedInputStream;
@@ -85,10 +87,17 @@
 
         mCropView = (CropView) findViewById(R.id.cropView);
 
-        Intent cropIntent = this.getIntent();
+        Intent cropIntent = getIntent();
         final Uri imageUri = cropIntent.getData();
 
-        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, 0), null);
+        if (imageUri == null) {
+            Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity");
+            finish();
+            return;
+        }
+
+        int rotation = getRotationFromExif(this, imageUri);
+        mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
         mCropView.setTouchEnabled(true);
         // Action bar
         // Show the custom action bar view
@@ -102,8 +111,6 @@
                         cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
                     }
                 });
-        getWindow().addPrivateFlags(
-                WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR);
     }
 
     public boolean enableRotation() {
@@ -170,9 +177,47 @@
         return new Point(defaultWidth, defaultHeight);
     }
 
+    public static int getRotationFromExif(String path) {
+        return getRotationFromExifHelper(path, null, 0, null, null);
+    }
+
+    public static int getRotationFromExif(Context context, Uri uri) {
+        return getRotationFromExifHelper(null, null, 0, context, uri);
+    }
+
+    public static int getRotationFromExif(Resources res, int resId) {
+        return getRotationFromExifHelper(null, res, resId, null, null);
+    }
+
+    private static int getRotationFromExifHelper(
+            String path, Resources res, int resId, Context context, Uri uri) {
+        ExifInterface ei = new ExifInterface();
+        try {
+            if (path != null) {
+                ei.readExif(path);
+            } else if (uri != null) {
+                InputStream is = context.getContentResolver().openInputStream(uri);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                ei.readExif(bis);
+            } else {
+                InputStream is = res.openRawResource(resId);
+                BufferedInputStream bis = new BufferedInputStream(is);
+                ei.readExif(bis);
+            }
+            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+            if (ori != null) {
+                return ExifInterface.getRotationForOrientationValue(ori.shortValue());
+            }
+        } catch (IOException e) {
+            Log.w(LOGTAG, "Getting exif data failed", e);
+        }
+        return 0;
+    }
+
     protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
-        BitmapCropTask cropTask = new BitmapCropTask(this,
-                filePath, null, 0, 0, true, false, null);
+        int rotation = getRotationFromExif(filePath);
+        BitmapCropTask cropTask = new BitmapCropTask(
+                this, filePath, null, rotation, 0, 0, true, false, null);
         final Point bounds = cropTask.getImageBounds();
         Runnable onEndCrop = new Runnable() {
             public void run() {
@@ -192,6 +237,7 @@
             Resources res, int resId, final boolean finishActivityWhenDone) {
         // crop this image and scale it down to the default wallpaper size for
         // this device
+        int rotation = getRotationFromExif(res, resId);
         Point inSize = mCropView.getSourceDimensions();
         Point outSize = getDefaultWallpaperSize(getResources(),
                 getWindowManager());
@@ -209,8 +255,7 @@
             }
         };
         BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
-                crop, outSize.x, outSize.y,
-                true, false, onEndCrop);
+                crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
         cropTask.execute();
     }
 
@@ -222,8 +267,6 @@
     protected void cropImageAndSetWallpaper(Uri uri,
             OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
         // Get the crop
-        Point inSize = mCropView.getSourceDimensions();
-
         boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
 
         Point minDims = new Point();
@@ -262,12 +305,21 @@
         }
         // Get the crop
         RectF cropRect = mCropView.getCrop();
+        int cropRotation = mCropView.getImageRotation();
         float cropScale = mCropView.getWidth() / (float) cropRect.width();
 
+        Point inSize = mCropView.getSourceDimensions();
+        Matrix rotateMatrix = new Matrix();
+        rotateMatrix.setRotate(cropRotation);
+        float[] rotatedInSize = new float[] { inSize.x, inSize.y };
+        rotateMatrix.mapPoints(rotatedInSize);
+        rotatedInSize[0] = Math.abs(rotatedInSize[0]);
+        rotatedInSize[1] = Math.abs(rotatedInSize[1]);
+
         // ADJUST CROP WIDTH
         // Extend the crop all the way to the right, for parallax
         // (or all the way to the left, in RTL)
-        float extraSpace = ltr ? inSize.x - cropRect.right : cropRect.left;
+        float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
         // Cap the amount of extra width
         float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
         extraSpace = Math.min(extraSpace, maxExtraSpace);
@@ -285,7 +337,7 @@
             float extraPortraitHeight =
                     portraitHeight / cropScale - cropRect.height();
             float expandHeight =
-                    Math.min(Math.min(inSize.y - cropRect.bottom, cropRect.top),
+                    Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
                             extraPortraitHeight / 2);
             cropRect.top -= expandHeight;
             cropRect.bottom += expandHeight;
@@ -303,7 +355,7 @@
             }
         };
         BitmapCropTask cropTask = new BitmapCropTask(this, uri,
-                cropRect, outWidth, outHeight, true, false, onEndCrop);
+                cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
         if (onBitmapCroppedHandler != null) {
             cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
         }
@@ -323,7 +375,7 @@
         InputStream mInStream;
         RectF mCropBounds = null;
         int mOutWidth, mOutHeight;
-        int mRotation = 0; // for now
+        int mRotation;
         String mOutputFormat = "jpg"; // for now
         boolean mSetWallpaper;
         boolean mSaveCroppedBitmap;
@@ -334,40 +386,45 @@
         boolean mNoCrop;
 
         public BitmapCropTask(Context c, String filePath,
-                RectF cropBounds, int outWidth, int outHeight,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mContext = c;
             mInFilePath = filePath;
-            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
         public BitmapCropTask(byte[] imageBytes,
-                RectF cropBounds, int outWidth, int outHeight,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mInImageBytes = imageBytes;
-            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
         public BitmapCropTask(Context c, Uri inUri,
-                RectF cropBounds, int outWidth, int outHeight,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mContext = c;
             mInUri = inUri;
-            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
         public BitmapCropTask(Context c, Resources res, int inResId,
-                RectF cropBounds, int outWidth, int outHeight,
+                RectF cropBounds, int rotation, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mContext = c;
             mInResId = inResId;
             mResources = res;
-            init(cropBounds, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+            init(cropBounds, rotation,
+                    outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
         }
 
-        private void init(RectF cropBounds, int outWidth, int outHeight,
+        private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
                 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
             mCropBounds = cropBounds;
+            mRotation = rotation;
             mOutWidth = outWidth;
             mOutHeight = outHeight;
             mSetWallpaper = setWallpaper;
@@ -454,6 +511,29 @@
             if (mInStream != null) {
                 // Find crop bounds (scaled to original image size)
                 Rect roundedTrueCrop = new Rect();
+                Matrix rotateMatrix = new Matrix();
+                Matrix inverseRotateMatrix = new Matrix();
+                if (mRotation > 0) {
+                    rotateMatrix.setRotate(mRotation);
+                    inverseRotateMatrix.setRotate(-mRotation);
+
+                    mCropBounds.roundOut(roundedTrueCrop);
+                    mCropBounds = new RectF(roundedTrueCrop);
+
+                    Point bounds = getImageBounds();
+
+                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+                    rotateMatrix.mapPoints(rotatedBounds);
+                    rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+                    rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+                    mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+                    inverseRotateMatrix.mapRect(mCropBounds);
+                    mCropBounds.offset(bounds.x/2, bounds.y/2);
+
+                    regenerateInputStream();
+                }
+
                 mCropBounds.roundOut(roundedTrueCrop);
 
                 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
@@ -497,6 +577,12 @@
                         fullSize = BitmapFactory.decodeStream(mInStream, null, options);
                     }
                     if (fullSize != null) {
+                        mCropBounds.left /= scaleDownSampleSize;
+                        mCropBounds.top /= scaleDownSampleSize;
+                        mCropBounds.bottom /= scaleDownSampleSize;
+                        mCropBounds.right /= scaleDownSampleSize;
+                        mCropBounds.roundOut(roundedTrueCrop);
+
                         crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
                                 roundedTrueCrop.top, roundedTrueCrop.width(),
                                 roundedTrueCrop.height());
@@ -508,16 +594,40 @@
                     failure = true;
                     return false;
                 }
-                if (mOutWidth > 0 && mOutHeight > 0) {
-                    Matrix m = new Matrix();
-                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
-                    if (mRotation > 0) {
-                        m.setRotate(mRotation);
-                        m.mapRect(cropRect);
+                if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
+                    float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
+                    rotateMatrix.mapPoints(dimsAfter);
+                    dimsAfter[0] = Math.abs(dimsAfter[0]);
+                    dimsAfter[1] = Math.abs(dimsAfter[1]);
+
+                    if (!(mOutWidth > 0 && mOutHeight > 0)) {
+                        mOutWidth = Math.round(dimsAfter[0]);
+                        mOutHeight = Math.round(dimsAfter[1]);
                     }
+
+                    RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
                     RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
-                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
-                    m.preRotate(mRotation);
+
+                    Matrix m = new Matrix();
+                    if (mRotation == 0) {
+                        m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+                    } else {
+                        Matrix m1 = new Matrix();
+                        m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
+                        Matrix m2 = new Matrix();
+                        m2.setRotate(mRotation);
+                        Matrix m3 = new Matrix();
+                        m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
+                        Matrix m4 = new Matrix();
+                        m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+
+                        Matrix c1 = new Matrix();
+                        c1.setConcat(m2, m1);
+                        Matrix c2 = new Matrix();
+                        c2.setConcat(m4, m3);
+                        m.setConcat(c2, c1);
+                    }
+
                     Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
                             (int) returnRect.height(), Bitmap.Config.ARGB_8888);
                     if (tmp != null) {
@@ -527,14 +637,6 @@
                         c.drawBitmap(crop, m, p);
                         crop = tmp;
                     }
-                } else if (mRotation > 0) {
-                    Matrix m = new Matrix();
-                    m.setRotate(mRotation);
-                    Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
-                            crop.getHeight(), m, true);
-                    if (tmp != null) {
-                        crop = tmp;
-                    }
                 }
 
                 if (mSaveCroppedBitmap) {
@@ -603,8 +705,7 @@
             final SharedPreferences sharedPrefs,
             WindowManager windowManager,
             final WallpaperManager wallpaperManager) {
-        final Point defaultWallpaperSize =
-                WallpaperCropActivity.getDefaultWallpaperSize(res, windowManager);
+        final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
 
         new Thread("suggestWallpaperDimension") {
             public void run() {
@@ -616,7 +717,6 @@
         }.start();
     }
 
-
     protected static RectF getMaxCropRect(
             int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
         RectF cropRect = new RectF();
diff --git a/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java
new file mode 100644
index 0000000..3e57a77
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+import java.util.Arrays;
+
+/**
+ *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
+ *  entering immersive mode.
+ */
+public class ImmersiveModeConfirmation {
+    private static final String TAG = "ImmersiveModeConfirmation";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
+
+    private final Context mContext;
+    private final H mHandler;
+    private final ArraySet<String> mConfirmedPackages = new ArraySet<String>();
+    private final long mShowDelayMs;
+    private final long mPanicThresholdMs;
+
+    private ClingWindowView mClingWindow;
+    private String mLastPackage;
+    private String mPromptPackage;
+    private long mPanicTime;
+    private String mPanicPackage;
+    private WindowManager mWindowManager;
+
+    public ImmersiveModeConfirmation(Context context) {
+        mContext = context;
+        mHandler = new H();
+        mShowDelayMs = getNavBarExitDuration() * 3;
+        mPanicThresholdMs = context.getResources()
+                .getInteger(R.integer.config_immersive_mode_confirmation_panic);
+        mWindowManager = (WindowManager)
+                mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    private long getNavBarExitDuration() {
+        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
+        return exit != null ? exit.getDuration() : 0;
+    }
+
+    public void loadSetting() {
+        if (DEBUG) Slog.d(TAG, "loadSetting()");
+        mConfirmedPackages.clear();
+        String packages = null;
+        try {
+            packages = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    UserHandle.USER_CURRENT);
+            if (packages != null) {
+                mConfirmedPackages.addAll(Arrays.asList(packages.split(",")));
+                if (DEBUG) Slog.d(TAG, "Loaded mConfirmedPackages=" + mConfirmedPackages);
+            }
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error loading confirmations, packages=" + packages, t);
+        }
+    }
+
+    private void saveSetting() {
+        if (DEBUG) Slog.d(TAG, "saveSetting()");
+        try {
+            final String packages = TextUtils.join(",", mConfirmedPackages);
+            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    packages,
+                    UserHandle.USER_CURRENT);
+            if (DEBUG) Slog.d(TAG, "Saved packages=" + packages);
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error saving confirmations, mConfirmedPackages=" + mConfirmedPackages, t);
+        }
+    }
+
+    public void immersiveModeChanged(String pkg, boolean isImmersiveMode) {
+        if (pkg == null) {
+            return;
+        }
+        mHandler.removeMessages(H.SHOW);
+        if (isImmersiveMode) {
+            mLastPackage = pkg;
+            if (DEBUG_SHOW_EVERY_TIME || !mConfirmedPackages.contains(pkg)) {
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW, pkg), mShowDelayMs);
+            }
+        } else {
+            mLastPackage = null;
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    public void onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode) {
+        if (mPanicPackage != null && !isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
+            // turning the screen back on within the panic threshold
+            unconfirmPackage(mPanicPackage);
+        }
+        if (isScreenOn && inImmersiveMode) {
+            // turning the screen off, remember if we were in immersive mode
+            mPanicTime = time;
+            mPanicPackage = mLastPackage;
+        } else {
+            mPanicTime = 0;
+            mPanicPackage = null;
+        }
+    }
+
+    public void confirmCurrentPrompt() {
+        mHandler.post(confirmAction(mPromptPackage));
+    }
+
+    private void unconfirmPackage(String pkg) {
+        if (pkg != null) {
+            if (DEBUG) Slog.d(TAG, "Unconfirming immersive mode confirmation for " + pkg);
+            mConfirmedPackages.remove(pkg);
+            saveSetting();
+        }
+    }
+
+    private void handleHide() {
+        if (mClingWindow != null) {
+            if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation for " + mPromptPackage);
+            mWindowManager.removeView(mClingWindow);
+            mClingWindow = null;
+        }
+    }
+
+    public WindowManager.LayoutParams getClingWindowLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_TOAST,
+                0
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                ,
+                PixelFormat.TRANSLUCENT);
+        lp.setTitle("ImmersiveModeConfirmation");
+        lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
+        lp.gravity = Gravity.FILL;
+        return lp;
+    }
+
+    public FrameLayout.LayoutParams getBubbleLayoutParams() {
+        return new FrameLayout.LayoutParams(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.immersive_mode_cling_width),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+    }
+
+    private class ClingWindowView extends FrameLayout {
+        private static final int BGCOLOR = 0x80000000;
+        private static final int OFFSET_DP = 48;
+
+        private final Runnable mConfirm;
+        private final ColorDrawable mColor = new ColorDrawable(0);
+        private ValueAnimator mColorAnim;
+        private ViewGroup mClingLayout;
+
+        private Runnable mUpdateLayoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mClingLayout != null && mClingLayout.getParent() != null) {
+                    mClingLayout.setLayoutParams(getBubbleLayoutParams());
+                }
+            }
+        };
+
+        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                    post(mUpdateLayoutRunnable);
+                }
+            }
+        };
+
+        public ClingWindowView(Context context, Runnable confirm) {
+            super(context);
+            mConfirm = confirm;
+            setClickable(true);
+            setBackground(mColor);
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            mWindowManager.getDefaultDisplay().getMetrics(metrics);
+            float density = metrics.density;
+
+            // create the confirmation cling
+            mClingLayout = (ViewGroup)
+                    View.inflate(getContext(), R.layout.immersive_mode_cling, null);
+
+            final Button ok = (Button) mClingLayout.findViewById(R.id.ok);
+            ok.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mConfirm.run();
+                }
+            });
+            addView(mClingLayout, getBubbleLayoutParams());
+
+            if (ActivityManager.isHighEndGfx()) {
+                final View bubble = mClingLayout.findViewById(R.id.text);
+                bubble.setAlpha(0f);
+                bubble.setTranslationY(-OFFSET_DP*density);
+                bubble.animate()
+                        .alpha(1f)
+                        .translationY(0)
+                        .setDuration(300)
+                        .setInterpolator(new DecelerateInterpolator())
+                        .start();
+
+                ok.setAlpha(0f);
+                ok.setTranslationY(-OFFSET_DP*density);
+                ok.animate().alpha(1f)
+                        .translationY(0)
+                        .setDuration(300)
+                        .setStartDelay(200)
+                        .setInterpolator(new DecelerateInterpolator())
+                        .start();
+
+                mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
+                mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        final int c = (Integer) animation.getAnimatedValue();
+                        mColor.setColor(c);
+                    }
+                });
+                mColorAnim.setDuration(1000);
+                mColorAnim.start();
+            } else {
+                mColor.setColor(BGCOLOR);
+            }
+
+            mContext.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            mContext.unregisterReceiver(mReceiver);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent motion) {
+            Slog.v(TAG, "ClingWindowView.onTouchEvent");
+            return true;
+        }
+    }
+
+    private void handleShow(String pkg) {
+        mPromptPackage = pkg;
+        if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation for " + pkg);
+
+        mClingWindow = new ClingWindowView(mContext, confirmAction(pkg));
+
+        // we will be hiding the nav bar, so layout as if it's already hidden
+        mClingWindow.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+              | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+        // show the confirmation
+        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        mWindowManager.addView(mClingWindow, lp);
+    }
+
+    private Runnable confirmAction(final String pkg) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                if (pkg != null && !mConfirmedPackages.contains(pkg)) {
+                    if (DEBUG) Slog.d(TAG, "Confirming immersive mode for " + pkg);
+                    mConfirmedPackages.add(pkg);
+                    saveSetting();
+                }
+                handleHide();
+            }
+        };
+    }
+
+    private final class H extends Handler {
+        private static final int SHOW = 0;
+        private static final int HIDE = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case SHOW:
+                    handleShow((String)msg.obj);
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index eba689d..1745bc7 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -23,6 +23,8 @@
 import static android.view.WindowManager.LayoutParams.*;
 
 import android.view.ViewConfiguration;
+
+import com.android.internal.R;
 import com.android.internal.view.RootViewSurfaceTaker;
 import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
@@ -1920,6 +1922,9 @@
         private PopupWindow mActionModePopup;
         private Runnable mShowActionModePopup;
 
+        // View added at runtime to IME windows to cover the navigation bar
+        private View mNavigationGuard;
+
         public DecorView(Context context, int featureId) {
             super(context);
             mFeatureId = featureId;
@@ -2479,6 +2484,33 @@
         @Override
         protected boolean fitSystemWindows(Rect insets) {
             mFrameOffsets.set(insets);
+
+            // IMEs lay out below the nav bar, but the content view must not (for back compat)
+            if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+                // prevent the content view from including the nav bar height
+                if (mContentParent != null) {
+                    if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+                        MarginLayoutParams mlp =
+                                (MarginLayoutParams) mContentParent.getLayoutParams();
+                        mlp.bottomMargin = insets.bottom;
+                        mContentParent.setLayoutParams(mlp);
+                    }
+                }
+                // position the navigation guard view, creating it if necessary
+                if (mNavigationGuard == null) {
+                    mNavigationGuard = new View(mContext);
+                    mNavigationGuard.setBackgroundColor(mContext.getResources()
+                            .getColor(R.color.input_method_navigation_guard));
+                    addView(mNavigationGuard, new LayoutParams(
+                            LayoutParams.MATCH_PARENT, insets.bottom,
+                            Gravity.START | Gravity.BOTTOM));
+                } else {
+                    LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+                    lp.height = insets.bottom;
+                    mNavigationGuard.setLayoutParams(lp);
+                }
+            }
+
             if (getForeground() != null) {
                 drawableChanged();
             }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 20d325a..020c1fd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -42,6 +42,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.media.AudioManager;
+import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -299,6 +300,7 @@
     int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
     boolean mHasSoftInput = false;
     boolean mTouchExplorationEnabled = false;
+    boolean mTranslucentDecorEnabled = true;
 
     int mPointerLocationMode = 0; // guarded by mLock
 
@@ -534,7 +536,7 @@
                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.Secure.TRANSIENT_NAV_CONFIRMATIONS), false, this,
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS), false, this,
                     UserHandle.USER_ALL);
             updateSettings();
         }
@@ -572,7 +574,7 @@
             StatusBarManager.WINDOW_NAVIGATION_BAR,
             WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
 
-    private TransientNavigationConfirmation mTransientNavigationConfirmation;
+    private ImmersiveModeConfirmation mImmersiveModeConfirmation;
 
     private SystemGesturesPointerEventListener mSystemGestures;
 
@@ -903,6 +905,8 @@
                 com.android.internal.R.integer.config_lidNavigationAccessibility);
         mLidControlsSleep = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_lidControlsSleep);
+        mTranslucentDecorEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableTranslucentDecor);
         readConfigurationDependentBehaviors();
 
         // register for dock events
@@ -955,7 +959,7 @@
                         // no-op
                     }
                 });
-        mTransientNavigationConfirmation = new TransientNavigationConfirmation(mContext);
+        mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
         mWindowManagerFuncs.registerPointerEventListener(mSystemGestures);
 
         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1173,8 +1177,8 @@
                 mHasSoftInput = hasSoftInput;
                 updateRotation = true;
             }
-            if (mTransientNavigationConfirmation != null) {
-                mTransientNavigationConfirmation.loadSetting();
+            if (mImmersiveModeConfirmation != null) {
+                mImmersiveModeConfirmation.loadSetting();
             }
         }
         if (updateRotation) {
@@ -2703,15 +2707,17 @@
             final int sysui = mLastSystemUiFlags;
             boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
             boolean navTranslucent = (sysui & View.NAVIGATION_BAR_TRANSLUCENT) != 0;
-            boolean transientAllowed = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
-            navTranslucent &= !transientAllowed;  // transient trumps translucent
-            navTranslucent &= isTranslucentNavigationAllowed();
+            boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+            boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+            boolean navAllowedHidden = immersive || immersiveSticky;
+            navTranslucent &= !immersiveSticky;  // transient trumps translucent
+            navTranslucent &= areTranslucentBarsAllowed();
 
             // When the navigation bar isn't visible, we put up a fake
             // input window to catch all touch events.  This way we can
             // detect when the user presses anywhere to bring back the nav
             // bar and ensure the application doesn't see the event.
-            if (navVisible || transientAllowed) {
+            if (navVisible || navAllowedHidden) {
                 if (mHideNavFakeWindow != null) {
                     mHideNavFakeWindow.dismiss();
                     mHideNavFakeWindow = null;
@@ -2742,7 +2748,7 @@
                             - mNavigationBarHeightForRotation[displayRotation];
                     mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
                     mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
-                    if (transientNavBarShowing || navTranslucent) {
+                    if (transientNavBarShowing) {
                         mNavigationBarController.setBarShowingLw(true);
                     } else if (navVisible) {
                         mNavigationBarController.setBarShowingLw(true);
@@ -2766,7 +2772,7 @@
                             - mNavigationBarWidthForRotation[displayRotation];
                     mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
                     mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
-                    if (transientNavBarShowing || navTranslucent) {
+                    if (transientNavBarShowing) {
                         mNavigationBarController.setBarShowingLw(true);
                     } else if (navVisible) {
                         mNavigationBarController.setBarShowingLw(true);
@@ -2826,6 +2832,7 @@
 
                 boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
                 boolean statusBarTranslucent = (sysui & View.STATUS_BAR_TRANSLUCENT) != 0;
+                statusBarTranslucent &= areTranslucentBarsAllowed();
 
                 // If the status bar is hidden, we don't want to cause
                 // windows behind it to scroll.
@@ -2996,8 +3003,10 @@
             pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
             pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
             pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
-            // IM dock windows always go above the nav bar.
-            pf.bottom = df.bottom = of.bottom = cf.bottom = vf.bottom = mStableBottom;
+            // IM dock windows layout below the nav bar...
+            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+            // ...with content insets above the nav bar
+            cf.bottom = vf.bottom = mStableBottom;
             // IM dock windows always go to the bottom of the screen.
             attrs.gravity = Gravity.BOTTOM;
             mDockLayer = win.getSurfaceLayer();
@@ -3077,8 +3086,9 @@
                                 + mOverscanScreenHeight;
                     } else if (canHideNavigationBar()
                             && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                            && (attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD || (
+                                attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+                             && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
                         // Asking for layout as if the nav bar is hidden, lets the
                         // application extend into the unrestricted overscan screen area.  We
                         // only do this for application windows to ensure no window that
@@ -3521,7 +3531,9 @@
                             | FINISH_LAYOUT_REDO_CONFIG
                             | FINISH_LAYOUT_REDO_WALLPAPER;
                 }
-                mKeyguardDelegate.setHidden(true);
+                if (!mShowingDream) {
+                    mKeyguardDelegate.setHidden(true);
+                }
             } else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) {
                 // This is the case of keyguard isSecure() and not mHideLockScreen.
                 if (mDismissKeyguard == DISMISS_KEYGUARD_START) {
@@ -3562,9 +3574,9 @@
     }
 
     public boolean allowAppAnimationsLw() {
-        if (mKeyguard != null && mKeyguard.isVisibleLw() && !mKeyguard.isAnimatingLw()) {
-            // If keyguard is currently visible, no reason to animate
-            // behind it.
+        if (mKeyguard != null && mKeyguard.isVisibleLw() && !mKeyguard.isAnimatingLw()
+                || mShowingDream) {
+            // If keyguard or dreams is currently visible, no reason to animate behind it.
             return false;
         }
         return true;
@@ -3648,7 +3660,9 @@
     }
 
     /**
-     * @return Whether music is being played right now.
+     * @return Whether music is being played right now "locally" (e.g. on the device's speakers
+     *    or wired headphones) or "remotely" (e.g. on a device using the Cast protocol and
+     *    controlled by this device, or through remote submix).
      */
     boolean isMusicActive() {
         final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -3656,7 +3670,7 @@
             Log.w(TAG, "isMusicActive: couldn't get AudioManager reference");
             return false;
         }
-        return am.isMusicActive();
+        return am.isLocalOrRemoteMusicActive();
     }
 
     /**
@@ -3669,19 +3683,28 @@
             return;
         }
         try {
-            // since audio is playing, we shouldn't have to hold a wake lock
+            // when audio is playing locally, we shouldn't have to hold a wake lock
             // during the call, but we do it as a precaution for the rare possibility
-            // that the music stops right before we call this
+            // that the music stops right before we call this.
+            // Otherwise we might also be in a remote playback case.
             // TODO: Actually handle MUTE.
             mBroadcastWakeLock.acquire();
-            audioService.adjustStreamVolume(stream,
-                keycode == KeyEvent.KEYCODE_VOLUME_UP
-                            ? AudioManager.ADJUST_RAISE
-                            : AudioManager.ADJUST_LOWER,
-                    0,
-                    mContext.getOpPackageName());
+            if (stream == AudioSystem.STREAM_MUSIC) {
+                audioService.adjustLocalOrRemoteStreamVolume(stream,
+                        keycode == KeyEvent.KEYCODE_VOLUME_UP
+                                ? AudioManager.ADJUST_RAISE
+                                : AudioManager.ADJUST_LOWER,
+                        mContext.getOpPackageName());
+            } else {
+                audioService.adjustStreamVolume(stream,
+                        keycode == KeyEvent.KEYCODE_VOLUME_UP
+                                ? AudioManager.ADJUST_RAISE
+                                : AudioManager.ADJUST_LOWER,
+                        0,
+                        mContext.getOpPackageName());
+            }
         } catch (RemoteException e) {
-            Log.w(TAG, "IAudioService.adjustStreamVolume() threw RemoteException " + e);
+            Log.w(TAG, "IAudioService.adjust*StreamVolume() threw RemoteException " + e);
         } finally {
             mBroadcastWakeLock.release();
         }
@@ -3934,8 +3957,8 @@
             case KeyEvent.KEYCODE_POWER: {
                 result &= ~ACTION_PASS_TO_USER;
                 if (down) {
-                    mTransientNavigationConfirmation.onPowerKeyDown(isScreenOn, event.getDownTime(),
-                            isTransientNavigationAllowed(mLastSystemUiFlags));
+                    mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, event.getDownTime(),
+                            isImmersiveMode(mLastSystemUiFlags));
                     if (isScreenOn && !mPowerKeyTriggered
                             && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                         mPowerKeyTriggered = true;
@@ -4214,7 +4237,7 @@
                 }
                 if (sb) mStatusBarController.showTransient();
                 if (nb) mNavigationBarController.showTransient();
-                mTransientNavigationConfirmation.confirmCurrentPrompt();
+                mImmersiveModeConfirmation.confirmCurrentPrompt();
                 updateSystemUiVisibilityLw();
             }
         }
@@ -4275,7 +4298,7 @@
 
     private void waitForKeyguardWindowDrawn(IBinder windowToken,
             final ScreenOnListener screenOnListener) {
-        if (windowToken != null) {
+        if (windowToken != null && !mHideLockScreen) {
             try {
                 if (mWindowManager.waitForWindowDrawn(
                         windowToken, new IRemoteCallback.Stub() {
@@ -4650,6 +4673,9 @@
 
     /** {@inheritDoc} */
     public void systemBooted() {
+        if (mKeyguardDelegate != null) {
+            mKeyguardDelegate.onBootCompleted();
+        }
         synchronized (mLock) {
             mSystemBooted = true;
         }
@@ -5100,71 +5126,86 @@
             int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                     | View.SYSTEM_UI_FLAG_IMMERSIVE
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                     | View.STATUS_BAR_TRANSLUCENT
                     | View.NAVIGATION_BAR_TRANSLUCENT;
             vis = (vis & ~flags) | (oldVis & flags);
         }
 
-        if (!isTranslucentNavigationAllowed()) {
-            vis &= ~View.NAVIGATION_BAR_TRANSLUCENT;
+        if (!areTranslucentBarsAllowed()) {
+            vis &= ~(View.NAVIGATION_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSLUCENT);
         }
 
         // update status bar
-        boolean transientAllowed =
-                (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+        boolean immersiveSticky =
+                (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
         boolean hideStatusBarWM =
                 mTopFullscreenOpaqueWindowState != null &&
                 (mTopFullscreenOpaqueWindowState.getAttrs().flags
                         & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
         boolean hideStatusBarSysui =
                 (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+        boolean hideNavBarSysui =
+                (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
 
         boolean transientStatusBarAllowed =
                 mStatusBar != null && (
                 hideStatusBarWM
-                || (hideStatusBarSysui && transientAllowed)
+                || (hideStatusBarSysui && immersiveSticky)
                 || statusBarHasFocus);
 
-        if (mStatusBarController.isTransientShowing()
-                && !transientStatusBarAllowed && hideStatusBarSysui) {
+        boolean transientNavBarAllowed =
+                mNavigationBar != null &&
+                hideNavBarSysui && immersiveSticky;
+
+        boolean denyTransientStatus = mStatusBarController.isTransientShowing()
+                && !transientStatusBarAllowed && hideStatusBarSysui;
+        boolean denyTransientNav = mNavigationBarController.isTransientShowing()
+                && !transientNavBarAllowed;
+        if (denyTransientStatus || denyTransientNav) {
             // clear the clearable flags instead
-            int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
-            if (newVal != mResettingSystemUiFlags) {
-                mResettingSystemUiFlags = newVal;
-                mWindowManagerFuncs.reevaluateStatusBarVisibility();
-            }
+            clearClearableFlagsLw();
         }
 
         vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
 
         // update navigation bar
-        boolean oldTransientNav = isTransientNavigationAllowed(oldVis);
-        boolean isTransientNav = isTransientNavigationAllowed(vis);
-        if (win != null && oldTransientNav != isTransientNav) {
+        boolean oldImmersiveMode = isImmersiveMode(oldVis);
+        boolean newImmersiveMode = isImmersiveMode(vis);
+        if (win != null && oldImmersiveMode != newImmersiveMode) {
             final String pkg = win.getOwningPackage();
-            mTransientNavigationConfirmation.transientNavigationChanged(pkg, isTransientNav);
+            mImmersiveModeConfirmation.immersiveModeChanged(pkg, newImmersiveMode);
         }
-        vis = mNavigationBarController.updateVisibilityLw(isTransientNav, oldVis, vis);
 
-        // don't send low profile updates if the system bars are hidden
-        if (mStatusBarController.isHidden() && mNavigationBarController.isHidden()) {
-            vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
-        }
+        vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
+
         return vis;
     }
 
-    private boolean isTransientNavigationAllowed(int vis) {
+    private void clearClearableFlagsLw() {
+        int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
+        if (newVal != mResettingSystemUiFlags) {
+            mResettingSystemUiFlags = newVal;
+            mWindowManagerFuncs.reevaluateStatusBarVisibility();
+        }
+    }
+
+    private boolean isImmersiveMode(int vis) {
+        final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
         return mNavigationBar != null
                 && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
-                && (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+                && (vis & flags) != 0
+                && canHideNavigationBar();
     }
 
     /**
-     * @return whether the navigation bar can be made translucent, e.g. touch
-     *         exploration is not enabled
+     * @return whether the navigation or status bar can be made translucent
+     *
+     * This should return true unless touch exploration is not enabled or
+     * R.boolean.config_enableTranslucentDecor is false.
      */
-    private boolean isTranslucentNavigationAllowed() {
-        return !mTouchExplorationEnabled;
+    private boolean areTranslucentBarsAllowed() {
+        return mTranslucentDecorEnabled && !mTouchExplorationEnabled;
     }
 
     // Use this instead of checking config_showNavigationBar so that it can be consistently
@@ -5181,6 +5222,11 @@
     }
 
     @Override
+    public int getInputMethodWindowVisibleHeightLw() {
+        return mDockBottom - mCurBottom;
+    }
+
+    @Override
     public void setCurrentUserLw(int newUserId) {
         mCurrentUserId = newUserId;
         if (mKeyguardDelegate != null) {
diff --git a/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java b/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java
deleted file mode 100644
index 8613088..0000000
--- a/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.policy.impl;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.Toast;
-
-import com.android.internal.R;
-
-import java.util.Arrays;
-
-/**
- *  Helper to manage showing/hiding a confirmation prompt when the transient navigation bar
- *  is hidden.
- */
-public class TransientNavigationConfirmation {
-    private static final String TAG = "TransientNavigationConfirmation";
-    private static final boolean DEBUG = false;
-
-    private final Context mContext;
-    private final H mHandler;
-    private final ArraySet<String> mConfirmedPackages = new ArraySet<String>();
-    private final long mShowDelayMs;
-    private final long mPanicThresholdMs;
-
-    private Toast mToast;
-    private String mLastPackage;
-    private String mPromptPackage;
-    private long mPanicTime;
-    private String mPanicPackage;
-
-    public TransientNavigationConfirmation(Context context) {
-        mContext = context;
-        mHandler = new H();
-        mShowDelayMs = getNavBarExitDuration() * 3;
-        mPanicThresholdMs = context.getResources()
-                .getInteger(R.integer.config_transient_navigation_confirmation_panic);
-    }
-
-    private long getNavBarExitDuration() {
-        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
-        return exit != null ? exit.getDuration() : 0;
-    }
-
-    public void loadSetting() {
-        if (DEBUG) Slog.d(TAG, "loadSetting()");
-        mConfirmedPackages.clear();
-        String packages = null;
-        try {
-            packages = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-                    Settings.Secure.TRANSIENT_NAV_CONFIRMATIONS,
-                    UserHandle.USER_CURRENT);
-            if (packages != null) {
-                mConfirmedPackages.addAll(Arrays.asList(packages.split(",")));
-                if (DEBUG) Slog.d(TAG, "Loaded mConfirmedPackages=" + mConfirmedPackages);
-            }
-        } catch (Throwable t) {
-            Slog.w(TAG, "Error loading confirmations, packages=" + packages, t);
-        }
-    }
-
-    private void saveSetting() {
-        if (DEBUG) Slog.d(TAG, "saveSetting()");
-        try {
-            final String packages = TextUtils.join(",", mConfirmedPackages);
-            Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                    Settings.Secure.TRANSIENT_NAV_CONFIRMATIONS,
-                    packages,
-                    UserHandle.USER_CURRENT);
-            if (DEBUG) Slog.d(TAG, "Saved packages=" + packages);
-        } catch (Throwable t) {
-            Slog.w(TAG, "Error saving confirmations, mConfirmedPackages=" + mConfirmedPackages, t);
-        }
-    }
-
-    public void transientNavigationChanged(String pkg, boolean isNavTransient) {
-        if (pkg == null) {
-            return;
-        }
-        mHandler.removeMessages(H.SHOW);
-        if (isNavTransient) {
-            mLastPackage = pkg;
-            if (!mConfirmedPackages.contains(pkg)) {
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW, pkg), mShowDelayMs);
-            }
-        } else {
-            mLastPackage = null;
-            mHandler.sendEmptyMessage(H.HIDE);
-        }
-    }
-
-    public void onPowerKeyDown(boolean isScreenOn, long time, boolean transientNavigationAllowed) {
-        if (mPanicPackage != null && !isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
-            // turning the screen back on within the panic threshold
-            unconfirmPackage(mPanicPackage);
-        }
-        if (isScreenOn && transientNavigationAllowed) {
-            // turning the screen off, remember if we were hiding the transient nav
-            mPanicTime = time;
-            mPanicPackage = mLastPackage;
-        } else {
-            mPanicTime = 0;
-            mPanicPackage = null;
-        }
-    }
-
-    public void confirmCurrentPrompt() {
-        mHandler.post(confirmAction(mPromptPackage));
-    }
-
-    private void unconfirmPackage(String pkg) {
-        if (pkg != null) {
-            if (DEBUG) Slog.d(TAG, "Unconfirming transient navigation for " + pkg);
-            mConfirmedPackages.remove(pkg);
-            saveSetting();
-        }
-    }
-
-    private void handleHide() {
-        if (mToast != null) {
-            if (DEBUG) Slog.d(TAG,
-                    "Hiding transient navigation confirmation for " + mPromptPackage);
-            mToast.cancel();
-            mToast = null;
-        }
-    }
-
-    private void handleShow(String pkg) {
-        mPromptPackage = pkg;
-        if (DEBUG) Slog.d(TAG, "Showing transient navigation confirmation for " + pkg);
-
-        // create the confirmation toast bar
-        final int msg = R.string.transient_navigation_confirmation;
-        mToast = Toast.makeBar(mContext, msg, Toast.LENGTH_INFINITE);
-        mToast.setAction(R.string.ok, confirmAction(pkg));
-
-        // we will be hiding the nav bar, so layout as if it's already hidden
-        mToast.getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
-        // show the confirmation
-        mToast.show();
-    }
-
-    private Runnable confirmAction(final String pkg) {
-        return new Runnable() {
-            @Override
-            public void run() {
-                if (pkg != null && !mConfirmedPackages.contains(pkg)) {
-                    if (DEBUG) Slog.d(TAG, "Confirming transient navigation for " + pkg);
-                    mConfirmedPackages.add(pkg);
-                    saveSetting();
-                }
-                handleHide();
-            }
-        };
-    }
-
-    private final class H extends Handler {
-        private static final int SHOW = 0;
-        private static final int HIDE = 1;
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case SHOW:
-                    handleShow((String)msg.obj);
-                    break;
-                case HIDE:
-                    handleHide();
-                    break;
-            }
-        }
-    }
-}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
index 56a282b..bf22e2f 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
@@ -52,6 +52,7 @@
         public int offReason;
         public int currentUser;
         public boolean screenIsOn;
+        public boolean bootCompleted;
     };
 
     public interface ShowListener {
@@ -117,6 +118,9 @@
                 // This is used to hide the scrim once keyguard displays.
                 mKeyguardService.onScreenTurnedOn(new KeyguardShowDelegate(null));
             }
+            if (mKeyguardState.bootCompleted) {
+                mKeyguardService.onBootCompleted();
+            }
         }
 
         @Override
@@ -305,4 +309,11 @@
         });
     }
 
+    public void onBootCompleted() {
+        if (mKeyguardService != null) {
+            mKeyguardService.onBootCompleted();
+        }
+        mKeyguardState.bootCompleted = true;
+    }
+
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
index 83be1a8..9fb2a50 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
@@ -180,6 +180,14 @@
         }
     }
 
+    public void onBootCompleted() {
+        try {
+            mService.onBootCompleted();
+        } catch (RemoteException e) {
+            Slog.w(TAG , "Remote Exception", e);
+        }
+    }
+
     public void showAssistant() {
         // Not used by PhoneWindowManager
     }
diff --git a/preloaded-classes b/preloaded-classes
index cb2ace3..467da25 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -76,7 +76,6 @@
 android.app.ActivityThread$ProviderKey
 android.app.ActivityThread$ProviderRefCount
 android.app.ActivityThread$ReceiverData
-android.app.ActivityThread$ResourcesKey
 android.app.ActivityThread$ResultData
 android.app.ActivityThread$ServiceArgsData
 android.app.ActivityThread$StopInfo
@@ -796,7 +795,6 @@
 android.preference.CheckBoxPreference
 android.preference.GenericInflater
 android.preference.GenericInflater$Parent
-android.preference.OnDependencyChangeListener
 android.preference.Preference
 android.preference.Preference$OnPreferenceChangeInternalListener
 android.preference.Preference$OnPreferenceChangeListener
@@ -971,7 +969,6 @@
 android.view.Choreographer$FrameDisplayEventReceiver
 android.view.Choreographer$FrameHandler
 android.view.CollapsibleActionView
-android.view.CompatibilityInfoHolder
 android.view.ContextMenu
 android.view.ContextMenu$ContextMenuInfo
 android.view.ContextThemeWrapper
@@ -1205,29 +1202,12 @@
 android.view.inputmethod.InputMethodManager$PendingEvent
 android.view.textservice.SpellCheckerSubtype
 android.view.textservice.SpellCheckerSubtype$1
-android.webkit.BrowserFrame
-android.webkit.BrowserFrame$ConfigCallback
-android.webkit.CallbackProxy
 android.webkit.CookieManager
 android.webkit.CookieSyncManager
-android.webkit.DeviceMotionAndOrientationManager
 android.webkit.GeolocationPermissions
-android.webkit.HTML5Audio
-android.webkit.HTML5VideoViewProxy
-android.webkit.JWebCoreJavaBridge
 android.webkit.JavascriptInterface
-android.webkit.L10nUtils
-android.webkit.MockGeolocation
-android.webkit.OverScrollGlow
-android.webkit.PluginManager
-android.webkit.QuadF
 android.webkit.URLUtil
-android.webkit.ViewManager
-android.webkit.ViewManager$2
-android.webkit.ViewManager$3
-android.webkit.ViewStateSerializer
 android.webkit.WebBackForwardList
-android.webkit.WebCoreThreadWatchdog
 android.webkit.WebHistoryItem
 android.webkit.WebIconDatabase
 android.webkit.WebSettings
@@ -1241,35 +1221,14 @@
 android.webkit.WebView
 android.webkit.WebView$PrivateAccess
 android.webkit.WebViewClient
-android.webkit.WebViewCore$AutoFillData
-android.webkit.WebViewCore$DrawData
-android.webkit.WebViewCore$EventHub
-android.webkit.WebViewCore$EventHub$1
-android.webkit.WebViewCore$GetUrlData
-android.webkit.WebViewCore$TextFieldInitData
-android.webkit.WebViewCore$ViewState
-android.webkit.WebViewCore$WebCoreThread
-android.webkit.WebViewCore$WebCoreThread$1
 android.webkit.WebViewDatabase
 android.webkit.WebViewFactory
 android.webkit.WebViewFactory$Preloader
 android.webkit.WebViewFactoryProvider
 android.webkit.WebViewFactoryProvider$Statics
-android.webkit.WebViewInputDispatcher
-android.webkit.WebViewInputDispatcher$DispatchEventQueue
-android.webkit.WebViewInputDispatcher$TouchStream
-android.webkit.WebViewInputDispatcher$UiCallbacks
-android.webkit.WebViewInputDispatcher$UiHandler
-android.webkit.WebViewInputDispatcher$WebKitCallbacks
-android.webkit.WebViewInputDispatcher$WebKitHandler
 android.webkit.WebViewProvider
 android.webkit.WebViewProvider$ScrollDelegate
 android.webkit.WebViewProvider$ViewDelegate
-android.webkit.ZoomControlBase
-android.webkit.ZoomControlExternal
-android.webkit.ZoomManager
-android.webkit.ZoomManager$FocusMovementQueue
-android.webkit.ZoomManager$PostScale
 android.widget.AbsListView
 android.widget.AbsListView$1
 android.widget.AbsListView$AdapterDataSetObserver
@@ -1313,10 +1272,8 @@
 android.widget.EditText
 android.widget.Editor
 android.widget.Editor$Blink
-android.widget.Editor$EasyEditSpanController
 android.widget.Editor$InputContentType
 android.widget.Editor$InputMethodState
-android.widget.Editor$UserDictionaryListener
 android.widget.ExpandableListView
 android.widget.Filter
 android.widget.Filter$FilterListener
@@ -1946,7 +1903,6 @@
 java.lang.UnsatisfiedLinkError
 java.lang.UnsupportedOperationException
 java.lang.VMClassLoader
-java.lang.VMThread
 java.lang.VerifyError
 java.lang.VirtualMachineError
 java.lang.Void
@@ -2310,7 +2266,6 @@
 java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock
 java.util.jar.Attributes
 java.util.jar.Attributes$Name
-java.util.jar.InitManifest
 java.util.jar.JarEntry
 java.util.jar.JarFile
 java.util.jar.JarFile$1JarFileEnumerator
diff --git a/services/input/Android.mk b/services/input/Android.mk
index 6e944ef..eb2bebe 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -42,6 +42,8 @@
 LOCAL_C_INCLUDES := \
     external/skia/include/core
 
+LOCAL_CFLAGS += -Wno-unused-parameter
+
 LOCAL_MODULE:= libinputservice
 
 LOCAL_MODULE_TAGS := optional
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index fd9c66b..2667a72 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -216,12 +216,12 @@
                 paint.setXfermodeMode(SkXfermode::kSrc_Mode);
                 surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
 
-                if (outBuffer.width > uint32_t(update.state.icon.bitmap.width())) {
+                if (outBuffer.width > update.state.icon.bitmap.width()) {
                     paint.setColor(0); // transparent fill color
                     surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
                             outBuffer.width, update.state.icon.bitmap.height(), paint);
                 }
-                if (outBuffer.height > uint32_t(update.state.icon.bitmap.height())) {
+                if (outBuffer.height > update.state.icon.bitmap.height()) {
                     paint.setColor(0); // transparent fill color
                     surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
                             outBuffer.width, outBuffer.height, paint);
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index d2d5280..546324a 100644
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -766,13 +766,17 @@
                 case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK:
                 {
                     IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
-                    mStateChangeCallbacks.register(callback);
+                    if (callback != null) {
+                        mStateChangeCallbacks.register(callback);
+                    }
                     break;
                 }
                 case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK:
                 {
                     IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
-                    mStateChangeCallbacks.unregister(callback);
+                    if (callback != null) {
+                        mStateChangeCallbacks.unregister(callback);
+                    }
                     break;
                 }
                 case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5695ee5..cb059ff 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -353,6 +353,11 @@
      */
     private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
 
+    /**
+     * PAC manager has received new port.
+     */
+    private static final int EVENT_PROXY_HAS_CHANGED = 16;
+
     /** Handler used for internal events. */
     private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -679,7 +684,7 @@
                 },
                 new IntentFilter(filter));
 
-        mPacManager = new PacManager(mContext);
+        mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
 
         filter = new IntentFilter();
         filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
@@ -2311,36 +2316,6 @@
         }
     }
 
-    private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
-        if (DBG) log("Captive portal check " + info);
-        int type = info.getType();
-        final NetworkStateTracker thisNet = mNetTrackers[type];
-        if (mNetConfigs[type].isDefault()) {
-            if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
-                if (isNewNetTypePreferredOverCurrentNetType(type)) {
-                    if (DBG) log("Captive check on " + info.getTypeName());
-                    mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info));
-                    return;
-                } else {
-                    if (DBG) log("Tear down low priority net " + info.getTypeName());
-                    teardown(thisNet);
-                    return;
-                }
-            }
-        }
-
-        if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info);
-        thisNet.captivePortalCheckComplete();
-    }
-
-    /** @hide */
-    @Override
-    public void captivePortalCheckComplete(NetworkInfo info) {
-        enforceConnectivityInternalPermission();
-        if (DBG) log("captivePortalCheckComplete: ni=" + info);
-        mNetTrackers[info.getType()].captivePortalCheckComplete();
-    }
-
     /** @hide */
     @Override
     public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
@@ -2967,9 +2942,6 @@
                     if (info.getDetailedState() ==
                             NetworkInfo.DetailedState.FAILED) {
                         handleConnectionFailure(info);
-                    } else if (info.getDetailedState() ==
-                            DetailedState.CAPTIVE_PORTAL_CHECK) {
-                        handleCaptivePortalTrackerCheck(info);
                     } else if (info.isConnectedToProvisioningNetwork()) {
                         /**
                          * TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
@@ -3124,6 +3096,10 @@
                     handleNetworkSamplingTimeout();
                     break;
                 }
+                case EVENT_PROXY_HAS_CHANGED: {
+                    handleApplyDefaultProxy((ProxyProperties)msg.obj);
+                    break;
+                }
             }
         }
     }
@@ -4238,27 +4214,35 @@
                         log("isMobileOk: linkHasIpv4=" + linkHasIpv4
                                 + " linkHasIpv6=" + linkHasIpv6);
 
-                        // Loop through at most 3 valid addresses or all of the address or until
-                        // we run out of time
-                        int loops = Math.min(3, addresses.length);
-                        for(int validAddr=0, addrTried=0;
-                                    (validAddr < loops) && (addrTried < addresses.length)
-                                      && (SystemClock.elapsedRealtime() < endTime);
-                                addrTried ++) {
+                        final ArrayList<InetAddress> validAddresses =
+                                new ArrayList<InetAddress>(addresses.length);
 
-                            // Choose the address at random but make sure its type is supported
-                            // TODO: This doesn't work 100% of the time, because we may end up
-                            // trying the same invalid address more than once and ignoring one
-                            // of the valid addresses.
-                            InetAddress hostAddr = addresses[rand.nextInt(addresses.length)];
-                            if (((hostAddr instanceof Inet4Address) && linkHasIpv4)
-                                    || ((hostAddr instanceof Inet6Address) && linkHasIpv6)) {
-                                // Valid address, so use it
-                                validAddr += 1;
-                            } else {
-                                // Invalid address so try next address
-                                continue;
+                        for (InetAddress addr : addresses) {
+                            if (((addr instanceof Inet4Address) && linkHasIpv4) ||
+                                    ((addr instanceof Inet6Address) && linkHasIpv6)) {
+                                validAddresses.add(addr);
                             }
+                        }
+
+                        if (validAddresses.size() == 0) {
+                            return CMP_RESULT_CODE_NO_CONNECTION;
+                        }
+
+                        int addrTried = 0;
+                        while (true) {
+                            // Loop through at most 3 valid addresses or until
+                            // we run out of time
+                            if (addrTried++ >= 3) {
+                                log("too many loops tried - giving up");
+                                break;
+                            }
+                            if (SystemClock.elapsedRealtime() >= endTime) {
+                                log("spend too much time - giving up");
+                                break;
+                            }
+
+                            InetAddress hostAddr = validAddresses.get(rand.nextInt(
+                                    validAddresses.size()));
 
                             // Make a route to host so we check the specific interface.
                             if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
@@ -4274,8 +4258,10 @@
                             }
 
                             // Rewrite the url to have numeric address to use the specific route.
+                            // Add a pointless random query param to fool proxies into not caching.
                             URL newUrl = new URL(orgUri.getScheme(),
-                                    hostAddr.getHostAddress(), orgUri.getPath());
+                                    hostAddr.getHostAddress(),
+                                    orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE));
                             log("isMobileOk: newUrl=" + newUrl);
 
                             HttpURLConnection urlConn = null;
@@ -4312,6 +4298,9 @@
                                     // occasions where a server returned 200 even though
                                     // the device didn't have a "warm" sim.
                                     log("isMobileOk: not expected responseCode=" + responseCode);
+                                    // TODO - it would be nice in the single-address case to do
+                                    // another DNS resolve here, but flushing the cache is a bit
+                                    // heavy-handed.
                                     result = CMP_RESULT_CODE_REDIRECTED;
                                 }
                             } catch (Exception e) {
@@ -4445,8 +4434,9 @@
             mdst.enableMobileProvisioning(url);
         } else {
             if (DBG) log("handleMobileProvisioningAction: on default network");
-            Intent newIntent =
-                    new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+            Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+                    Intent.CATEGORY_APP_BROWSER);
+            newIntent.setData(Uri.parse(url));
             newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
                     Intent.FLAG_ACTIVITY_NEW_TASK);
             try {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 794d274..b64f5df 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -2237,6 +2237,11 @@
         return;
     }
 
+    @Override
+    public int getInputMethodWindowVisibleHeight() {
+        return mWindowManagerService.getInputMethodWindowVisibleHeight();
+    }
+
     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
             if (token == null) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 3e8770e..8f480dd 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1473,7 +1473,7 @@
                 + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
         LocationProviderInterface provider = mProvidersByName.get(name);
         if (provider == null) {
-            throw new IllegalArgumentException("provider doesn't exist: " + provider);
+            throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
 
         UpdateRecord record = new UpdateRecord(name, request, receiver);
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index c7ca1ea..e60231a 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -62,6 +62,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.NativeDaemonConnector.Command;
@@ -173,6 +174,8 @@
          * 600 series - Unsolicited broadcasts.
          */
         public static final int VolumeStateChange              = 605;
+        public static final int VolumeUuidChange               = 613;
+        public static final int VolumeUserLabelChange          = 614;
         public static final int VolumeDiskInserted             = 630;
         public static final int VolumeDiskRemoved              = 631;
         public static final int VolumeBadRemoval               = 632;
@@ -661,6 +664,7 @@
         final String oldState;
         synchronized (mVolumesLock) {
             oldState = mVolumeStates.put(path, state);
+            volume.setState(state);
         }
 
         if (state.equals(oldState)) {
@@ -801,6 +805,26 @@
             notifyVolumeStateChange(
                     cooked[2], cooked[3], Integer.parseInt(cooked[7]),
                             Integer.parseInt(cooked[10]));
+        } else if (code == VoldResponseCode.VolumeUuidChange) {
+            // Format: nnn <label> <path> <uuid>
+            final String path = cooked[2];
+            final String uuid = (cooked.length > 3) ? cooked[3] : null;
+
+            final StorageVolume vol = mVolumesByPath.get(path);
+            if (vol != null) {
+                vol.setUuid(uuid);
+            }
+
+        } else if (code == VoldResponseCode.VolumeUserLabelChange) {
+            // Format: nnn <label> <path> <label>
+            final String path = cooked[2];
+            final String userLabel = (cooked.length > 3) ? cooked[3] : null;
+
+            final StorageVolume vol = mVolumesByPath.get(path);
+            if (vol != null) {
+                vol.setUserLabel(userLabel);
+            }
+
         } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
                    (code == VoldResponseCode.VolumeDiskRemoved) ||
                    (code == VoldResponseCode.VolumeBadRemoval)) {
@@ -1230,6 +1254,7 @@
 
                             // Until we hear otherwise, treat as unmounted
                             mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
+                            volume.setState(Environment.MEDIA_UNMOUNTED);
                         }
                     }
 
@@ -1273,6 +1298,7 @@
         } else {
             // Place stub status for early callers to find
             mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
+            volume.setState(Environment.MEDIA_MOUNTED);
         }
     }
 
@@ -2743,54 +2769,59 @@
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump ActivityManager from from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " without permission " + android.Manifest.permission.DUMP);
-            return;
-        }
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
 
         synchronized (mObbMounts) {
-            pw.println("  mObbMounts:");
-
-            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator();
+            pw.println("mObbMounts:");
+            pw.increaseIndent();
+            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
+                    .iterator();
             while (binders.hasNext()) {
                 Entry<IBinder, List<ObbState>> e = binders.next();
-                pw.print("    Key="); pw.println(e.getKey().toString());
+                pw.println(e.getKey() + ":");
+                pw.increaseIndent();
                 final List<ObbState> obbStates = e.getValue();
                 for (final ObbState obbState : obbStates) {
-                    pw.print("      "); pw.println(obbState.toString());
+                    pw.println(obbState);
                 }
+                pw.decreaseIndent();
             }
+            pw.decreaseIndent();
 
-            pw.println("");
-            pw.println("  mObbPathToStateMap:");
+            pw.println();
+            pw.println("mObbPathToStateMap:");
+            pw.increaseIndent();
             final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
             while (maps.hasNext()) {
                 final Entry<String, ObbState> e = maps.next();
-                pw.print("    "); pw.print(e.getKey());
-                pw.print(" -> "); pw.println(e.getValue().toString());
+                pw.print(e.getKey());
+                pw.print(" -> ");
+                pw.println(e.getValue());
             }
+            pw.decreaseIndent();
         }
 
-        pw.println("");
-
         synchronized (mVolumesLock) {
-            pw.println("  mVolumes:");
-
-            final int N = mVolumes.size();
-            for (int i = 0; i < N; i++) {
-                final StorageVolume v = mVolumes.get(i);
-                pw.print("    ");
-                pw.println(v.toString());
-                pw.println("      state=" + mVolumeStates.get(v.getPath()));
+            pw.println();
+            pw.println("mVolumes:");
+            pw.increaseIndent();
+            for (StorageVolume volume : mVolumes) {
+                pw.println(volume);
+                pw.increaseIndent();
+                pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
+                pw.decreaseIndent();
             }
+            pw.decreaseIndent();
         }
 
         pw.println();
-        pw.println("  mConnection:");
+        pw.println("mConnection:");
+        pw.increaseIndent();
         mConnector.dump(fd, pw, args);
+        pw.decreaseIndent();
     }
 
     /** {@inheritDoc} */
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index b881934..0438675 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -1167,11 +1167,19 @@
                     }
                     if (packageChanged) {
                         // We cancel notifications for packages which have just been disabled
-                        final int enabled = mContext.getPackageManager()
-                                .getApplicationEnabledSetting(pkgName);
-                        if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                                || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
-                            cancelNotifications = false;
+                        try {
+                            final int enabled = mContext.getPackageManager()
+                                    .getApplicationEnabledSetting(pkgName);
+                            if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                                    || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+                                cancelNotifications = false;
+                            }
+                        } catch (IllegalArgumentException e) {
+                            // Package doesn't exist; probably racing with uninstall.
+                            // cancelNotifications is already true, so nothing to do here.
+                            if (DBG) {
+                                Slog.i(TAG, "Exception trying to look up app enabled setting", e);
+                            }
                         }
                     }
                     pkgList = new String[]{pkgName};
@@ -1542,11 +1550,9 @@
     private void scheduleTimeoutLocked(ToastRecord r)
     {
         mHandler.removeCallbacksAndMessages(r);
-        if (r.duration != Toast.LENGTH_INFINITE) {
-            Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
-            long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
-            mHandler.sendMessageDelayed(m, delay);
-        }
+        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
+        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
+        mHandler.sendMessageDelayed(m, delay);
     }
 
     private void handleTimeout(ToastRecord record)
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 1b8876d..a99b58a 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -736,9 +736,9 @@
                         + "there is at least one pointer down!");
             }
             case MotionEvent.ACTION_UP: {
+                mAms.onTouchInteractionEnd();
                 // Announce the end of a the touch interaction.
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-                mAms.onTouchInteractionEnd();
                 mLongPressingPointerId = -1;
                 mLongPressingPointerDeltaX = 0;
                 mLongPressingPointerDeltaY = 0;
@@ -822,6 +822,7 @@
         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
         if (accessibilityManager.isEnabled()) {
             AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            event.setWindowId(mAms.getActiveWindowId());
             accessibilityManager.sendAccessibilityEvent(event);
             switch (type) {
                 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index dd9ae4c..f972f70 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -293,17 +293,16 @@
         return mUserManager;
     }
 
-    private UserAccounts initUser(int userId) {
-        synchronized (mUsers) {
-            UserAccounts accounts = mUsers.get(userId);
-            if (accounts == null) {
-                accounts = new UserAccounts(mContext, userId);
-                mUsers.append(userId, accounts);
-                purgeOldGrants(accounts);
-                validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
-            }
-            return accounts;
+    /* Caller should lock mUsers */
+    private UserAccounts initUserLocked(int userId) {
+        UserAccounts accounts = mUsers.get(userId);
+        if (accounts == null) {
+            accounts = new UserAccounts(mContext, userId);
+            mUsers.append(userId, accounts);
+            purgeOldGrants(accounts);
+            validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
         }
+        return accounts;
     }
 
     private void purgeOldGrantsAll() {
@@ -427,7 +426,7 @@
         synchronized (mUsers) {
             UserAccounts accounts = mUsers.get(userId);
             if (accounts == null) {
-                accounts = initUser(userId);
+                accounts = initUserLocked(userId);
                 mUsers.append(userId, accounts);
             }
             return accounts;
@@ -1798,16 +1797,14 @@
 
     private AccountAndUser[] getAccounts(int[] userIds) {
         final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
-        synchronized (mUsers) {
-            for (int userId : userIds) {
-                UserAccounts userAccounts = getUserAccounts(userId);
-                if (userAccounts == null) continue;
-                synchronized (userAccounts.cacheLock) {
-                    Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
-                            Binder.getCallingUid(), null);
-                    for (int a = 0; a < accounts.length; a++) {
-                        runningAccounts.add(new AccountAndUser(accounts[a], userId));
-                    }
+        for (int userId : userIds) {
+            UserAccounts userAccounts = getUserAccounts(userId);
+            if (userAccounts == null) continue;
+            synchronized (userAccounts.cacheLock) {
+                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
+                        Binder.getCallingUid(), null);
+                for (int a = 0; a < accounts.length; a++) {
+                    runningAccounts.add(new AccountAndUser(accounts[a], userId));
                 }
             }
         }
@@ -2170,15 +2167,20 @@
                  * this can be very bad when those apps are in the system like
                  * the System Settings.
                  */
-                PackageManager pm = mContext.getPackageManager();
-                ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
-                int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
                 int authenticatorUid = Binder.getCallingUid();
-                if (PackageManager.SIGNATURE_MATCH !=
-                        pm.checkSignatures(authenticatorUid, targetUid)) {
-                    throw new SecurityException(
-                            "Activity to be started with KEY_INTENT must " +
-                            "share Authenticator's signatures");
+                long bid = Binder.clearCallingIdentity();
+                try {
+                    PackageManager pm = mContext.getPackageManager();
+                    ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                    int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+                    if (PackageManager.SIGNATURE_MATCH !=
+                            pm.checkSignatures(authenticatorUid, targetUid)) {
+                        throw new SecurityException(
+                                "Activity to be started with KEY_INTENT must " +
+                               "share Authenticator's signatures");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(bid);
                 }
             }
             if (result != null
@@ -2858,7 +2860,8 @@
                 || callingUid == Process.myUid()) {
             return unfiltered;
         }
-        if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) {
+        UserInfo user = mUserManager.getUserInfo(userAccounts.userId);
+        if (user != null && user.isRestricted()) {
             String[] packages = mPackageManager.getPackagesForUid(callingUid);
             // If any of the packages is a white listed package, return the full set,
             // otherwise return non-shared accounts only.
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index b69a0c8..a64940c 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -1494,7 +1494,7 @@
                     } catch (Exception e) {
                         Slog.w(TAG, "Exception when unbinding service "
                                 + r.shortName, e);
-                        serviceDoneExecutingLocked(r, true, true);
+                        serviceProcessGoneLocked(r);
                     }
                 }
             }
@@ -1544,7 +1544,7 @@
                 } catch (Exception e) {
                     Slog.w(TAG, "Exception when destroying service "
                             + r.shortName, e);
-                    serviceDoneExecutingLocked(r, true, true);
+                    serviceProcessGoneLocked(r);
                 }
                 updateServiceForegroundLocked(r.app, false);
             } else {
@@ -1570,7 +1570,7 @@
             r.tracker.setStarted(false, memFactor, now);
             r.tracker.setBound(false, memFactor, now);
             if (r.executeNesting == 0) {
-                r.tracker.clearCurrentOwner(r);
+                r.tracker.clearCurrentOwner(r, false);
                 r.tracker = null;
             }
         }
@@ -1629,7 +1629,7 @@
                     s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
                 } catch (Exception e) {
                     Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
-                    serviceDoneExecutingLocked(s, true, true);
+                    serviceProcessGoneLocked(s);
                 }
             }
 
@@ -1708,6 +1708,16 @@
         }
     }
 
+    private void serviceProcessGoneLocked(ServiceRecord r) {
+        if (r.tracker != null) {
+            int memFactor = mAm.mProcessStats.getMemFactorLocked();
+            long now = SystemClock.uptimeMillis();
+            r.tracker.setExecuting(false, memFactor, now);
+            r.tracker.setBound(false, memFactor, now);
+        }
+        serviceDoneExecutingLocked(r, true, true);
+    }
+
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
             boolean finishing) {
         if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r
@@ -1747,7 +1757,7 @@
                 r.tracker.setExecuting(false, mAm.mProcessStats.getMemFactorLocked(),
                         SystemClock.uptimeMillis());
                 if (finishing) {
-                    r.tracker.clearCurrentOwner(r);
+                    r.tracker.clearCurrentOwner(r, false);
                     r.tracker = null;
                 }
             }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5a6e24b..33362bb 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -226,7 +226,7 @@
     static final boolean DEBUG_RESULTS = localLOGV || false;
     static final boolean DEBUG_SERVICE = localLOGV || false;
     static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
-    static final boolean DEBUG_STACK = localLOGV || true;
+    static final boolean DEBUG_STACK = localLOGV || false;
     static final boolean DEBUG_SWITCH = localLOGV || false;
     static final boolean DEBUG_TASKS = localLOGV || false;
     static final boolean DEBUG_THUMBNAILS = localLOGV || false;
@@ -236,7 +236,7 @@
     static final boolean DEBUG_VISBILITY = localLOGV || false;
     static final boolean DEBUG_PSS = localLOGV || false;
     static final boolean DEBUG_LOCKSCREEN = localLOGV || false;
-    static final boolean VALIDATE_TOKENS = true;
+    static final boolean VALIDATE_TOKENS = false;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
 
     // Control over CPU and battery monitoring.
@@ -3470,7 +3470,7 @@
         }
 
         // Remove this application's activities from active lists.
-        boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app, restarting);
+        boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
 
         app.activities.clear();
 
@@ -6417,9 +6417,8 @@
             final int callingUid = Binder.getCallingUid();
             final UriPermission perm = findUriPermissionLocked(callingUid, uri);
             if (perm == null) {
-                Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri "
-                        + uri.toSafeString());
-                return;
+                throw new SecurityException("No permission grant found for UID " + callingUid
+                        + " and Uri " + uri.toSafeString());
             }
 
             boolean persistChanged = perm.takePersistableModes(modeFlags);
@@ -6493,26 +6492,53 @@
     }
 
     @Override
-    public ParceledListSlice<android.content.UriPermission> getPersistedUriPermissions() {
+    public ParceledListSlice<android.content.UriPermission> getPersistedUriPermissions(
+            String packageName, boolean incoming) {
         enforceNotIsolatedCaller("getPersistedUriPermissions");
+        Preconditions.checkNotNull(packageName, "packageName");
 
+        final int callingUid = Binder.getCallingUid();
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            final int packageUid = pm.getPackageUid(packageName, UserHandle.getUserId(callingUid));
+            if (packageUid != callingUid) {
+                throw new SecurityException(
+                        "Package " + packageName + " does not belong to calling UID " + callingUid);
+            }
+        } catch (RemoteException e) {
+            throw new SecurityException("Failed to verify package name ownership");
+        }
+
+        final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
         synchronized (this) {
-            final int callingUid = Binder.getCallingUid();
-            final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
-            final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
-            if (perms == null) {
-                Slog.w(TAG, "No permission grants found for UID " + callingUid);
+            if (incoming) {
+                final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+                if (perms == null) {
+                    Slog.w(TAG, "No permission grants found for " + packageName);
+                } else {
+                    final int size = perms.size();
+                    for (int i = 0; i < size; i++) {
+                        final UriPermission perm = perms.valueAt(i);
+                        if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
+                            result.add(perm.buildPersistedPublicApiObject());
+                        }
+                    }
+                }
             } else {
-                final int size = perms.size();
+                final int size = mGrantedUriPermissions.size();
                 for (int i = 0; i < size; i++) {
-                    final UriPermission perm = perms.valueAt(i);
-                    if (perm.persistedModeFlags != 0) {
-                        result.add(perm.buildPersistedPublicApiObject());
+                    final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+                    final int permsSize = perms.size();
+                    for (int j = 0; j < permsSize; j++) {
+                        final UriPermission perm = perms.valueAt(j);
+                        if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
+                            result.add(perm.buildPersistedPublicApiObject());
+                        }
                     }
                 }
             }
-            return new ParceledListSlice<android.content.UriPermission>(result);
         }
+        return new ParceledListSlice<android.content.UriPermission>(result);
     }
 
     @Override
@@ -6617,6 +6643,10 @@
         return list;
     }
 
+    TaskRecord getMostRecentTask() {
+        return mRecentTasks.get(0);
+    }
+
     @Override
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
             int flags, int userId) {
@@ -7532,7 +7562,7 @@
                         // Use existing process if already started
                         ProcessRecord proc = getProcessRecordLocked(
                                 cpi.processName, cpr.appInfo.uid, false);
-                        if (proc != null) {
+                        if (proc != null && proc.thread != null) {
                             if (DEBUG_PROVIDER) {
                                 Slog.d(TAG, "Installing in existing process " + proc);
                             }
@@ -13118,8 +13148,8 @@
                             boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(
                                     intent.getAction());
                             if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
-                                forceStopPackageLocked(ssp,
-                                        intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true,
+                                forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                        intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true,
                                         false, userId, removed ? "pkg removed" : "pkg changed");
                             }
                             if (removed) {
@@ -14245,6 +14275,8 @@
             }
         }
 
+        boolean mayBeTop = false;
+
         for (int is = app.services.size()-1;
                 is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
@@ -14405,18 +14437,27 @@
                             if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
                                 schedGroup = Process.THREAD_GROUP_DEFAULT;
                             }
-                            if (clientProcState <=
-                                    ActivityManager.PROCESS_STATE_PERSISTENT_UI &&
-                                    clientProcState >=
-                                            ActivityManager.PROCESS_STATE_PERSISTENT) {
-                                // Persistent processes don't allow us to become top.
-                                // However the top process DOES allow us to become top,
-                                // because in that case we are running because the current
-                                // top process wants us, so we should be counted as part
-                                // of the top set and not just running for some random
-                                // unknown reason in the background.
-                                clientProcState =
-                                        ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                            if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                                if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                                    // Special handling of clients who are in the top state.
+                                    // We *may* want to consider this process to be in the
+                                    // top state as well, but only if there is not another
+                                    // reason for it to be running.  Being on the top is a
+                                    // special state, meaning you are specifically running
+                                    // for the current top app.  If the process is already
+                                    // running in the background for some other reason, it
+                                    // is more important to continue considering it to be
+                                    // in the background state.
+                                    mayBeTop = true;
+                                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                                } else {
+                                    // Special handling for above-top states (persistent
+                                    // processes).  These should not bring the current process
+                                    // into the top state, since they are not on top.  Instead
+                                    // give them the best state after that.
+                                    clientProcState =
+                                            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                                }
                             }
                         } else {
                             if (clientProcState <
@@ -14504,18 +14545,27 @@
                     app.adjSourceOom = clientAdj;
                     app.adjTarget = cpr.name;
                 }
-                if (clientProcState <=
-                        ActivityManager.PROCESS_STATE_PERSISTENT_UI &&
-                        clientProcState >=
-                                ActivityManager.PROCESS_STATE_PERSISTENT) {
-                    // Persistent processes don't allow us to become top.
-                    // However the top process DOES allow us to become top,
-                    // because in that case we are running because the current
-                    // top process wants us, so we should be counted as part
-                    // of the top set and not just running for some random
-                    // unknown reason in the background.
-                    clientProcState =
-                            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                    if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                        // Special handling of clients who are in the top state.
+                        // We *may* want to consider this process to be in the
+                        // top state as well, but only if there is not another
+                        // reason for it to be running.  Being on the top is a
+                        // special state, meaning you are specifically running
+                        // for the current top app.  If the process is already
+                        // running in the background for some other reason, it
+                        // is more important to continue considering it to be
+                        // in the background state.
+                        mayBeTop = true;
+                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                    } else {
+                        // Special handling for above-top states (persistent
+                        // processes).  These should not bring the current process
+                        // into the top state, since they are not on top.  Instead
+                        // give them the best state after that.
+                        clientProcState =
+                                ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    }
                 }
                 if (procState > clientProcState) {
                     procState = clientProcState;
@@ -14542,6 +14592,28 @@
             }
         }
 
+        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
+            // A client of one of our services or providers is in the top state.  We
+            // *may* want to be in the top state, but not if we are already running in
+            // the background for some other reason.  For the decision here, we are going
+            // to pick out a few specific states that we want to remain in when a client
+            // is top (states that tend to be longer-term) and otherwise allow it to go
+            // to the top state.
+            switch (procState) {
+                case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+                case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+                case ActivityManager.PROCESS_STATE_SERVICE:
+                    // These all are longer-term states, so pull them up to the top
+                    // of the background states, but not all the way to the top state.
+                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    break;
+                default:
+                    // Otherwise, top is a better choice, so take it.
+                    procState = ActivityManager.PROCESS_STATE_TOP;
+                    break;
+            }
+        }
+
         if (adj == ProcessList.SERVICE_ADJ) {
             if (doingAll) {
                 app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index fc83f05..0397fd5 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -478,37 +478,45 @@
         }
         final int userId = UserHandle.getUserId(info.applicationInfo.uid);
 
+        if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.userId != userId) {
                 // Looking for a different task.
+                if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
                 continue;
             }
             final ActivityRecord r = task.getTopActivity();
             if (r == null || r.finishing || r.userId != userId ||
                     r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": mismatch root " + r);
                 continue;
             }
 
-            //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString()
-            //        + "/aff=" + r.task.affinity + " to new cls="
-            //        + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity);
+            if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls="
+                    + r.task.intent.getComponent().flattenToShortString()
+                    + "/aff=" + r.task.affinity + " to new cls="
+                    + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
             if (task.affinity != null) {
                 if (task.affinity.equals(info.taskAffinity)) {
-                    //Slog.i(TAG, "Found matching affinity!");
+                    if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
                     return r;
                 }
             } else if (task.intent != null && task.intent.getComponent().equals(cls)) {
-                //Slog.i(TAG, "Found matching class!");
+                if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
-                //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent);
+                if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
+                        + r.intent);
                 return r;
             } else if (task.affinityIntent != null
                     && task.affinityIntent.getComponent().equals(cls)) {
-                //Slog.i(TAG, "Found matching class!");
+                if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
-                //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent);
+                if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
+                        + r.intent);
                 return r;
+            } else if (DEBUG_TASKS) {
+                Slog.d(TAG, "Not a match: " + task);
             }
         }
 
@@ -551,9 +559,6 @@
      * Move the activities around in the stack to bring a user to the foreground.
      */
     final void switchUserLocked(int userId) {
-        if (VALIDATE_TOKENS) {
-            validateAppTokensLocked();
-        }
         if (mCurrentUser == userId) {
             return;
         }
@@ -564,11 +569,16 @@
         for (int i = 0; i < index; ++i) {
             TaskRecord task = mTaskHistory.get(i);
             if (task.userId == userId) {
+                if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
+                        " moving " + task + " to top");
                 mTaskHistory.remove(i);
                 mTaskHistory.add(task);
                 --index;
             }
         }
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
     }
 
     void minimalResumeActivityLocked(ActivityRecord r) {
@@ -581,6 +591,7 @@
         mService.addRecentTaskLocked(r.task);
         completeResumeLocked(r);
         mStackSupervisor.checkReadyForSleepLocked();
+        setLaunchTime(r);
         if (DEBUG_SAVED_STATE) Slog.i(TAG, "Launch completed; removing icicle of " + r.icicle);
     }
 
@@ -674,8 +685,8 @@
         }
 
         TaskRecord tr = who.task;
-        if (tr.intent != null && (tr.intent.getFlags()
-                &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
+        if (mService.getMostRecentTask() != tr && tr.intent != null &&
+                (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
             // If this task is being excluded from recents, we don't want to take
             // the expense of capturing a thumbnail, since we will never show it.
             return null;
@@ -985,7 +996,7 @@
      */
     final boolean ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
             String onlyThisProcess, int configChanges, boolean forceHomeShown) {
-        if (true || DEBUG_VISBILITY) Slog.v(
+        if (DEBUG_VISBILITY) Slog.v(
                 TAG, "ensureActivitiesVisible behind " + top
                 + " configChanges=0x" + Integer.toHexString(configChanges));
 
@@ -1041,7 +1052,7 @@
                                 r.startFreezingScreenLocked(r.app, configChanges);
                             }
                             if (!r.visible) {
-                                if (true || DEBUG_VISBILITY) Slog.v(
+                                if (DEBUG_VISBILITY) Slog.v(
                                         TAG, "Starting and making visible: " + r);
                                 mWindowManager.setAppVisibility(r.appToken, true);
                             }
@@ -1063,7 +1074,7 @@
                         if (r.state != ActivityState.RESUMED && r != starting) {
                             // If this activity is paused, tell it
                             // to now show its window.
-                            if (true || DEBUG_VISBILITY) Slog.v(
+                            if (DEBUG_VISBILITY) Slog.v(
                                     TAG, "Making visible and scheduling visibility: " + r);
                             try {
                                 if (mTranslucentActivityWaiting != null) {
@@ -1117,7 +1128,7 @@
                     // Now for any activities that aren't visible to the user, make
                     // sure they no longer are keeping the screen frozen.
                     if (r.visible) {
-                        if (true || DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r);
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r);
                         r.visible = false;
                         try {
                             mWindowManager.setAppVisibility(r.appToken, false);
@@ -1629,10 +1640,12 @@
     private void insertTaskAtTop(TaskRecord task) {
         // If this is being moved to the top by another activity or being launched from the home
         // activity, set mOnTopOfHome accordingly.
-        final boolean fromHome = mStackSupervisor.getLastStack().isHomeStack();
+        ActivityStack lastStack = mStackSupervisor.getLastStack();
+        final boolean fromHome = lastStack == null ? true : lastStack.isHomeStack();
         if (!isHomeStack() && (fromHome || topTask() != task)) {
             task.mOnTopOfHome = fromHome;
         }
+
         mTaskHistory.remove(task);
         // Now put task at top.
         int stackNdx = mTaskHistory.size();
@@ -2628,6 +2641,9 @@
         if (task != null && task.removeActivity(r)) {
             if (DEBUG_STACK) Slog.i(TAG,
                     "removeActivityFromHistoryLocked: last activity removed from " + this);
+            if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) {
+                mStackSupervisor.moveHomeToTop();
+            }
             mStackSupervisor.removeTask(task);
         }
         r.takeFromHistory();
@@ -2945,6 +2961,7 @@
         for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.isHomeTask()) {
+                if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
                 mTaskHistory.remove(taskNdx);
                 mTaskHistory.add(top, task);
                 mWindowManager.moveTaskToTop(task.taskId);
@@ -3566,6 +3583,7 @@
 
     @Override
     public String toString() {
-        return "stackId=" + mStackId + " tasks=" + mTaskHistory;
+        return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
+                + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
     }
 }
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index b4de258..523015d 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -68,9 +68,8 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
 
-import android.util.SparseBooleanArray;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.os.TransferPipe;
 import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
@@ -89,7 +88,7 @@
     static final boolean DEBUG_ADD_REMOVE = DEBUG || false;
     static final boolean DEBUG_APP = DEBUG || false;
     static final boolean DEBUG_SAVED_STATE = DEBUG || false;
-    static final boolean DEBUG_STATES = DEBUG || true;
+    static final boolean DEBUG_STATES = DEBUG || false;
     static final boolean DEBUG_IDLE = DEBUG || false;
 
     public static final int HOME_STACK_ID = 0;
@@ -204,8 +203,8 @@
      */
     final PowerManager.WakeLock mGoingToSleep;
 
-    /** State of the stacks when user switched, indexed by userId. */
-    SparseBooleanArray mUserHomeInFront = new SparseBooleanArray(2);
+    /** Stack id of the front stack when user switched, indexed by userId. */
+    SparseIntArray mUserStackInFront = new SparseIntArray(2);
 
     public ActivityStackSupervisor(ActivityManagerService service, Context context,
             Looper looper) {
@@ -278,14 +277,18 @@
         }
     }
 
-    boolean resumeHomeActivity(ActivityRecord prev) {
+    void moveHomeToTop() {
         moveHomeStack(true);
+        mHomeStack.moveHomeTaskToTop();
+    }
+
+    boolean resumeHomeActivity(ActivityRecord prev) {
+        moveHomeToTop();
         if (prev != null) {
             prev.task.mOnTopOfHome = false;
         }
-        mHomeStack.moveHomeTaskToTop();
         ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
-        if (r != null) {
+        if (r != null && r.isHomeActivity()) {
             mService.setFocusedActivityLocked(r);
             return resumeTopActivitiesLocked(mHomeStack, prev, null);
         }
@@ -625,7 +628,7 @@
     }
 
     void startHomeActivity(Intent intent, ActivityInfo aInfo) {
-        moveHomeStack(true);
+        moveHomeToTop();
         startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
                 null, false, null);
     }
@@ -872,7 +875,7 @@
             throws RemoteException {
 
         r.startFreezingScreenLocked(app, 0);
-        if (true) Slog.d(TAG, "realStartActivity: setting app visibility true");
+        if (false) Slog.d(TAG, "realStartActivity: setting app visibility true");
         mWindowManager.setAppVisibility(r.appToken, true);
 
         // schedule launch ticks to collect information about slow apps.
@@ -1374,12 +1377,23 @@
         }
 
         final ActivityStack sourceStack;
-        TaskRecord sourceTask;
         if (sourceRecord != null) {
-            sourceTask = sourceRecord.task;
-            sourceStack = sourceTask.stack;
+            if (sourceRecord.finishing) {
+                // If the source is finishing, we can't further count it as our source.  This
+                // is because the task it is associated with may now be empty and on its way out,
+                // so we don't want to blindly throw it in to that task.  Instead we will take
+                // the NEW_TASK flow and try to find a task for it.
+                if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+                    Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+                            + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
+                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                }
+                sourceRecord = null;
+                sourceStack = null;
+            } else {
+                sourceStack = sourceRecord.task.stack;
+            }
         } else {
-            sourceTask = null;
             sourceStack = null;
         }
 
@@ -1421,6 +1435,8 @@
                     }
                     targetStack = intentActivity.task.stack;
                     targetStack.mLastPausedActivity = null;
+                    if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack
+                            + " from " + intentActivity);
                     moveHomeStack(targetStack.isHomeStack());
                     if (intentActivity.task.intent == null) {
                         // This task was started because of movement of
@@ -1660,7 +1676,7 @@
                 }
             }
         } else if (sourceRecord != null) {
-            sourceTask = sourceRecord.task;
+            TaskRecord sourceTask = sourceRecord.task;
             targetStack = sourceTask.stack;
             moveHomeStack(targetStack.isHomeStack());
             if (!addingToTask &&
@@ -1680,7 +1696,7 @@
                         targetStack.resumeTopActivityLocked(null);
                     }
                     ActivityOptions.abort(options);
-                    if (r.task == null)  Slog.v(TAG,
+                    if (r.task == null)  Slog.w(TAG,
                         "startActivityUncheckedLocked: task left null",
                         new RuntimeException("here").fillInStackTrace());
                     return ActivityManager.START_DELIVERED_TO_TOP;
@@ -1709,7 +1725,7 @@
             // it.
             r.setTask(sourceTask, sourceRecord.thumbHolder, false);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
-                    + " in existing task " + r.task);
+                    + " in existing task " + r.task + " from source " + sourceRecord);
 
         } else {
             // This not being started from an existing activity, and not part
@@ -1906,7 +1922,7 @@
         return r;
     }
 
-    boolean handleAppDiedLocked(ProcessRecord app, boolean restarting) {
+    boolean handleAppDiedLocked(ProcessRecord app) {
         boolean hasVisibleActivities = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             hasVisibleActivities |= mStacks.get(stackNdx).handleAppDiedLocked(app);
@@ -1923,7 +1939,7 @@
     }
 
     void removeUserLocked(int userId) {
-        mUserHomeInFront.delete(userId);
+        mUserStackInFront.delete(userId);
     }
 
     /**
@@ -2057,9 +2073,11 @@
     }
 
     ActivityRecord findTaskLocked(ActivityRecord r) {
+        if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r);
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
             if (!r.isApplicationActivity() && !stack.isHomeStack()) {
+                if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack);
                 continue;
             }
             final ActivityRecord ar = stack.findTaskLocked(r);
@@ -2067,6 +2085,7 @@
                 return ar;
             }
         }
+        if (DEBUG_TASKS) Slog.d(TAG, "No task found");
         return null;
     }
 
@@ -2244,8 +2263,8 @@
     }
 
     boolean switchUserLocked(int userId, UserStartedState uss) {
-        mUserHomeInFront.put(mCurrentUser, isFrontStack(mHomeStack));
-        final boolean homeInFront = mUserHomeInFront.get(userId, true);
+        mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
+        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
         mCurrentUser = userId;
 
         mStartingUsers.add(uss);
@@ -2253,7 +2272,13 @@
             mStacks.get(stackNdx).switchUserLocked(userId);
         }
 
+        ActivityStack stack = getStack(restoreStackId);
+        if (stack == null) {
+            stack = mHomeStack;
+        }
+        final boolean homeInFront = stack.isHomeStack();
         moveHomeStack(homeInFront);
+        mWindowManager.moveTaskToTop(stack.topTask().taskId);
         return homeInFront;
     }
 
@@ -2266,7 +2291,7 @@
         final boolean nowVisible = allResumedActivitiesVisible();
         for (int i=0; i<N; i++) {
             ActivityRecord s = mStoppingActivities.get(i);
-            if (true || localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible="
+            if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible="
                     + nowVisible + " waitingVisible=" + s.waitingVisible
                     + " finishing=" + s.finishing);
             if (s.waitingVisible && nowVisible) {
@@ -2347,7 +2372,7 @@
         pw.print(prefix); pw.print("mStackState="); pw.println(stackStateToString(mStackState));
         pw.print(prefix); pw.println("mSleepTimeout: " + mSleepTimeout);
         pw.print(prefix); pw.println("mCurTaskId: " + mCurTaskId);
-        pw.print(prefix); pw.println("mUserHomeInFront: " + mUserHomeInFront);
+        pw.print(prefix); pw.println("mUserStackInFront: " + mUserStackInFront);
     }
 
     ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 1d6970f..5e80135 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -54,9 +54,9 @@
     static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT;
     static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
 
-    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 25;
+    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
     static final int MAX_BROADCAST_SUMMARY_HISTORY
-            = ActivityManager.isLowRamDeviceStatic() ? 25 : 100;
+            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
 
     final ActivityManagerService mService;
 
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index a6375e1..50a7b5c 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -473,11 +473,11 @@
                     current.setDataPosition(0);
                     ProcessStats stats = ProcessStats.CREATOR.createFromParcel(current);
                     current.recycle();
-                    int i = 0;
-                    while (i < files.size() && (stats.mTimePeriodEndRealtime
+                    int i = files.size()-1;
+                    while (i >= 0 && (stats.mTimePeriodEndRealtime
                             - stats.mTimePeriodStartRealtime) < minTime) {
                         AtomicFile file = new AtomicFile(new File(files.get(i)));
-                        i++;
+                        i--;
                         ProcessStats moreStats = new ProcessStats(false);
                         readLocked(moreStats, file);
                         if (moreStats.mReadError == null) {
@@ -490,7 +490,7 @@
                                     - moreStats.mTimePeriodStartRealtime, sb);
                             Slog.i(TAG, sb.toString());
                         } else {
-                            Slog.w(TAG, "Failure reading " + files.get(i-1) + "; "
+                            Slog.w(TAG, "Failure reading " + files.get(i+1) + "; "
                                     + moreStats.mReadError);
                             continue;
                         }
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index c47c1ac..cc1172a 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -335,12 +335,7 @@
 
     public void forceClearTracker() {
         if (tracker != null) {
-            int memFactor = ams.mProcessStats.getMemFactorLocked();
-            long now = SystemClock.uptimeMillis();
-            tracker.setStarted(false, memFactor, now);
-            tracker.setBound(false, memFactor, now);
-            tracker.setExecuting(false, memFactor, now);
-            tracker.clearCurrentOwner(this);
+            tracker.clearCurrentOwner(this, true);
             tracker = null;
         }
     }
diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java
index 5868c08..1f12b74 100644
--- a/services/java/com/android/server/am/UriPermission.java
+++ b/services/java/com/android/server/am/UriPermission.java
@@ -20,9 +20,7 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.util.Log;
-import android.util.Slog;
 
-import com.android.internal.util.Preconditions;
 import com.google.android.collect.Sets;
 
 import java.io.PrintWriter;
@@ -132,7 +130,11 @@
      * @return if mode changes should trigger persisting.
      */
     boolean takePersistableModes(int modeFlags) {
-        Preconditions.checkFlagsArgument(modeFlags, persistableModeFlags);
+        if ((modeFlags & persistableModeFlags) != modeFlags) {
+            throw new SecurityException("Requested flags 0x"
+                    + Integer.toHexString(modeFlags) + ", but only 0x"
+                    + Integer.toHexString(persistableModeFlags) + " are allowed");
+        }
 
         final int before = persistedModeFlags;
         persistedModeFlags |= (persistableModeFlags & modeFlags);
diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java
index 1cb2fe3..7786fe6 100644
--- a/services/java/com/android/server/connectivity/PacManager.java
+++ b/services/java/com/android/server/connectivity/PacManager.java
@@ -27,6 +27,7 @@
 import android.net.Proxy;
 import android.net.ProxyProperties;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -89,6 +90,9 @@
     private boolean mHasSentBroadcast;
     private boolean mHasDownloaded;
 
+    private Handler mConnectivityHandler;
+    private int mProxyMessage;
+
     /**
      * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
      */
@@ -128,7 +132,7 @@
         }
     }
 
-    public PacManager(Context context) {
+    public PacManager(Context context, Handler handler, int proxyMessage) {
         mContext = context;
         mLastPort = -1;
 
@@ -136,6 +140,8 @@
                 context, 0, new Intent(ACTION_PAC_REFRESH), 0);
         context.registerReceiver(new PacRefreshIntentReceiver(),
                 new IntentFilter(ACTION_PAC_REFRESH));
+        mConnectivityHandler = handler;
+        mProxyMessage = proxyMessage;
     }
 
     private AlarmManager getAlarmManager() {
@@ -156,6 +162,10 @@
      */
     public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) {
         if (!TextUtils.isEmpty(proxy.getPacFileUrl())) {
+            if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
+                // Allow to send broadcast, nothing to do.
+                return false;
+            }
             synchronized (mProxyLock) {
                 mPacUrl = proxy.getPacFileUrl();
             }
@@ -356,16 +366,7 @@
     }
 
     private void sendPacBroadcast(ProxyProperties proxy) {
-        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
-            Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
     }
 
     private synchronized void sendProxyIfNeeded() {
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index f5a7039..2ca2cc5 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -597,10 +597,10 @@
         int appId = UserHandle.getAppId(Binder.getCallingUid());
         final long token = Binder.clearCallingIdentity();
         try {
-            // System dialogs are also allowed to control VPN.
+            // System VPN dialogs are also allowed to control VPN.
             PackageManager pm = mContext.getPackageManager();
             ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
-            if (appId == app.uid) {
+            if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) {
                 return;
             }
         } catch (Exception e) {
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index 48b3259..11c3fa0 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -42,6 +42,7 @@
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -344,12 +345,9 @@
      * and
      *   anonymous OR provider sync.
      * Depending on the request, we enqueue to suit in the SyncManager.
-     * @param request
+     * @param request The request object. Validation of this object is done by its builder.
      */
     public void sync(SyncRequest request) {
-        Bundle extras = request.getBundle();
-        ContentResolver.validateSyncExtrasBundle(extras);
-
         int userId = UserHandle.getCallingUserId();
         int callerUid = Binder.getCallingUid();
         // This makes it so that future permission checks will be in the context of this
@@ -357,8 +355,11 @@
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
-            if (syncManager == null) return;
+            if (syncManager == null) {
+                return;
+            }
 
+            Bundle extras = request.getBundle();
             long flextime = request.getSyncFlexTime();
             long runAtTime = request.getSyncRunTime();
             if (request.isPeriodic()) {
@@ -412,9 +413,13 @@
      * null.
      * @param cname cancel syncs running on this service, or null for provider/account.
      */
+    @Override
     public void cancelSync(Account account, String authority, ComponentName cname) {
-        int userId = UserHandle.getCallingUserId();
+        if (authority != null && authority.length() == 0) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
 
+        int userId = UserHandle.getCallingUserId();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
@@ -491,8 +496,8 @@
     public boolean getSyncAutomatically(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -508,10 +513,13 @@
 
     @Override
     public void setSyncAutomatically(Account account, String providerName, boolean sync) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -525,12 +533,19 @@
     }
 
     /** Old API. Schedule periodic sync with default flex time. */
+    @Override
     public void addPeriodicSync(Account account, String authority, Bundle extras,
             long pollFrequency) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty.");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         if (pollFrequency < 60) {
             Slog.w(TAG, "Requested poll frequency of " + pollFrequency
                     + " seconds being rounded up to 60 seconds.");
@@ -553,10 +568,16 @@
     }
 
     public void removePeriodicSync(Account account, String authority, Bundle extras) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             getSyncManager().getSyncStorageEngine()
@@ -568,13 +589,20 @@
         }
     }
 
+
     public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
             ComponentName cname) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
-        int callerUid = Binder.getCallingUid();
 
+        int callerUid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             if (cname == null) {
@@ -611,10 +639,13 @@
     }
 
     public void setIsSyncable(Account account, String providerName, int syncable) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -670,8 +701,8 @@
     public boolean getMasterSyncAutomatically() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -688,8 +719,8 @@
     public void setMasterSyncAutomatically(boolean flag) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
@@ -729,8 +760,8 @@
     public List<SyncInfo> getCurrentSyncs() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-        int userId = UserHandle.getCallingUserId();
 
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
@@ -740,8 +771,12 @@
     }
 
     public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
+
         int userId = UserHandle.getCallingUserId();
         int callerUid = Binder.getCallingUid();
         long identityToken = clearCallingIdentity();
@@ -768,8 +803,9 @@
     public boolean isSyncPending(Account account, String authority, ComponentName cname) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
-        int userId = UserHandle.getCallingUserId();
+
         int callerUid = Binder.getCallingUid();
+        int userId = UserHandle.getCallingUserId();
         long identityToken = clearCallingIdentity();
         SyncManager syncManager = getSyncManager();
         if (syncManager == null) return false;
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 5c0f902..18bf115 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -68,6 +68,7 @@
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -2149,6 +2150,13 @@
             for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
                 final AuthorityInfo authorityInfo = info.first;
                 final SyncStatusInfo status = info.second;
+
+                if (TextUtils.isEmpty(authorityInfo.target.provider)) {
+                    Log.e(TAG, "Got an empty provider string. Skipping: "
+                        + authorityInfo.target.provider);
+                    continue;
+                }
+
                 if (!isDispatchable(authorityInfo.target)) {
                     continue;
                 }
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index 1d8ca5a..e99adc5 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -1486,9 +1486,9 @@
      */
     public SyncStatusInfo getStatusByAuthority(EndPoint info) {
         if (info.target_provider && (info.account == null || info.provider == null)) {
-          throw new IllegalArgumentException();
+            return null;
         } else if (info.target_service && info.service == null) {
-            throw new IllegalArgumentException();
+            return null;
         }
         synchronized (mAuthorities) {
             final int N = mSyncStatus.size();
@@ -2669,7 +2669,7 @@
         } else {
             SyncRequest.Builder req =
                     new SyncRequest.Builder()
-                            .syncOnce(0, 0)
+                            .syncOnce()
                             .setExtras(extras);
             if (authorityInfo.target.target_provider) {
                 req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 2a93dfc..fb533c6 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -5566,9 +5566,9 @@
                         // version of the one on the data partition, but which
                         // granted a new system permission that it didn't have
                         // before.  In this case we do want to allow the app to
-                        // now get the new permission if the new system-partition
-                        // apk is privileged to get it.
-                        if (sysPs.pkg != null && isPrivilegedApp(pkg)) {
+                        // now get the new permission if the ancestral apk is
+                        // privileged to get it.
+                        if (sysPs.pkg != null && sysPs.isPrivileged()) {
                             for (int j=0;
                                     j<sysPs.pkg.requestedPermissions.size(); j++) {
                                 if (perm.equals(
@@ -9370,7 +9370,7 @@
         }
     }
 
-    boolean locationIsPrivileged(File path) {
+    static boolean locationIsPrivileged(File path) {
         try {
             final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
                     .getCanonicalPath();
diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java
index b6f9f5b..b447861 100644
--- a/services/java/com/android/server/pm/PackageSetting.java
+++ b/services/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
 
 import java.io.File;
@@ -56,4 +57,8 @@
     public int[] getGids() {
         return sharedUser != null ? sharedUser.gids : gids;
     }
+
+    public boolean isPrivileged() {
+        return (pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+    }
 }
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 7d0b31e..42d615d 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -2210,7 +2210,11 @@
 
         int pkgFlags = 0;
         pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
-        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
+        final File codePathFile = new File(codePathStr);
+        if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+            pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
+        }
+        PackageSetting ps = new PackageSetting(name, realName, codePathFile,
                 new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags);
         String timeStampStr = parser.getAttributeValue(null, "ft");
         if (timeStampStr != null) {
@@ -2266,6 +2270,7 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
+
         mDisabledSysPackages.put(name, ps);
     }
 
@@ -2473,7 +2478,8 @@
                 } else if (tagName.equals("signing-keyset")) {
                     long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
                     packageSetting.keySetData.addSigningKeySet(id);
-                    Slog.d(TAG, "Adding signing keyset " + Long.toString(id) + " to " + name);
+                    if (false) Slog.d(TAG, "Adding signing keyset " + Long.toString(id)
+                            + " to " + name);
                 } else if (tagName.equals("defined-keyset")) {
                     long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
                     String alias = parser.getAttributeValue(null, "alias");
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index 8fbde14..da9548f 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -747,6 +747,21 @@
     }
 
     @Override // Binder call
+    public void updateWakeLockUids(IBinder lock, int[] uids) {
+        WorkSource ws = null;
+
+        if (uids != null) {
+            ws = new WorkSource();
+            // XXX should WorkSource have a way to set uids as an int[] instead of adding them
+            // one at a time?
+            for (int i = 0; i < uids.length; i++) {
+                ws.add(uids[i]);
+            }
+        }
+        updateWakeLockWorkSource(lock, ws);
+    }
+
+    @Override // Binder call
     public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
         if (lock == null) {
             throw new IllegalArgumentException("lock must not be null");
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index b8e1b04..8a3997a 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -31,10 +31,10 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.print.IPrintClient;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintJobStateChangeListener;
 import android.print.IPrintManager;
@@ -45,6 +45,7 @@
 import android.print.PrinterId;
 import android.printservice.PrintServiceInfo;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -96,19 +97,19 @@
     }
 
     @Override
-    public PrintJobInfo print(String printJobName, final IPrintClient client,
-            final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
-            int appId, int userId) {
+    public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
+            PrintAttributes attributes, String packageName, int appId, int userId) {
         final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
         final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+        String resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName);
         final UserState userState;
         synchronized (mLock) {
             userState = getOrCreateUserStateLocked(resolvedUserId);
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            return userState.print(printJobName, client, documentAdapter,
-                    attributes, resolvedAppId);
+            return userState.print(printJobName, adapter, attributes,
+                    resolvedPackageName, resolvedAppId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -360,12 +361,17 @@
         }
 
         synchronized (mLock) {
-            pw.println("PRINT MANAGER STATE (dumpsys print)");
-            final int userStateCount = mUserStates.size();
-            for (int i = 0; i < userStateCount; i++) {
-                UserState userState = mUserStates.get(i);
-                userState.dump(fd, pw, "");
-                pw.println();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                pw.println("PRINT MANAGER STATE (dumpsys print)");
+                final int userStateCount = mUserStates.size();
+                for (int i = 0; i < userStateCount; i++) {
+                    UserState userState = mUserStates.get(i);
+                    userState.dump(fd, pw, "");
+                    pw.println();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
             }
         }
     }
@@ -473,7 +479,8 @@
                     ComponentName component = new ComponentName(serviceInfo.packageName,
                             serviceInfo.name);
                     String label = serviceInfo.loadLabel(mContext.getPackageManager()).toString();
-                    showEnableInstalledPrintServiceNotification(component, label);
+                    showEnableInstalledPrintServiceNotification(component, label,
+                            getChangingUserId());
                 }
             }
 
@@ -605,13 +612,30 @@
                 + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF.");
     }
 
+    private String resolveCallingPackageNameEnforcingSecurity(String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            return null;
+        }
+        String[] packages = mContext.getPackageManager().getPackagesForUid(
+                Binder.getCallingUid());
+        final int packageCount = packages.length;
+        for (int i = 0; i < packageCount; i++) {
+            if (packageName.equals(packages[i])) {
+                return packageName;
+            }
+        }
+        return null;
+    }
+
     private void showEnableInstalledPrintServiceNotification(ComponentName component,
-            String label) {
+            String label, int userId) {
+        UserHandle userHandle = new UserHandle(userId);
+
         Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
         intent.putExtra(EXTRA_PRINT_SERVICE_COMPONENT_NAME, component.flattenToString());
 
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null);
+        PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle);
 
         Notification.Builder builder = new Notification.Builder(mContext)
                 .setSmallIcon(R.drawable.ic_print)
@@ -626,6 +650,7 @@
                 .getSystemService(Context.NOTIFICATION_SERVICE);
 
         String notificationTag = getClass().getName() + ":" + component.flattenToString();
-        notificationManager.notify(notificationTag, 0, builder.build());
+        notificationManager.notifyAsUser(notificationTag, 0, builder.build(),
+                userHandle);
     }
 }
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 5b9dc28..1bb61d2 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -682,7 +682,7 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return service.mSpooler.getPrintJobInfos(service.mComponentName,
-                            PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS, PrintManager.APP_ID_ANY);
+                            PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index 798cea3..ffe9806 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -26,8 +26,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.print.IPrintClient;
-import android.print.IPrintDocumentAdapter;
 import android.print.IPrintSpooler;
 import android.print.IPrintSpoolerCallbacks;
 import android.print.IPrintSpoolerClient;
@@ -130,15 +128,14 @@
         return null;
     }
 
-    public final void createPrintJob(PrintJobInfo printJob, IPrintClient client,
-            IPrintDocumentAdapter documentAdapter) {
+    public final void createPrintJob(PrintJobInfo printJob) {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
             throwIfDestroyedLocked();
             mCanUnbind = false;
         }
         try {
-            getRemoteInstanceLazy().createPrintJob(printJob, client, documentAdapter);
+            getRemoteInstanceLazy().createPrintJob(printJob);
         } catch (RemoteException re) {
             Slog.e(LOG_TAG, "Error creating print job.", re);
         } catch (TimeoutException te) {
@@ -255,6 +252,31 @@
         return false;
     }
 
+    public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+        throwIfCalledOnMainThread();
+        synchronized (mLock) {
+            throwIfDestroyedLocked();
+            mCanUnbind = false;
+        }
+        try {
+            getRemoteInstanceLazy().setPrintJobCancelling(printJobId,
+                    cancelling);
+        } catch (RemoteException re) {
+            Slog.e(LOG_TAG, "Error setting print job cancelling.", re);
+        } catch (TimeoutException te) {
+            Slog.e(LOG_TAG, "Error setting print job cancelling.", te);
+        } finally {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+                        + "] setPrintJobCancelling()");
+            }
+            synchronized (mLock) {
+                mCanUnbind = true;
+                mLock.notifyAll();
+            }
+        }
+    }
+
     public final void removeObsoletePrintJobs() {
         throwIfCalledOnMainThread();
         synchronized (mLock) {
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index b3f0036..b69dcee 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -16,16 +16,20 @@
 
 package com.android.server.print;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -33,7 +37,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.print.IPrintClient;
+import android.os.UserHandle;
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintJobStateChangeListener;
 import android.print.IPrinterDiscoveryObserver;
@@ -44,6 +48,7 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.printservice.PrintServiceInfo;
+import android.provider.DocumentsContract;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -158,9 +163,9 @@
         mSpooler.removeObsoletePrintJobs();
     }
 
-    public PrintJobInfo print(String printJobName, final IPrintClient client,
-            final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
-            int appId) {
+    @SuppressWarnings("deprecation")
+    public Bundle print(String printJobName, IPrintDocumentAdapter adapter,
+            PrintAttributes attributes, String packageName, int appId) {
         // Create print job place holder.
         final PrintJobInfo printJob = new PrintJobInfo();
         printJob.setId(new PrintJobId());
@@ -169,9 +174,10 @@
         printJob.setAttributes(attributes);
         printJob.setState(PrintJobInfo.STATE_CREATED);
         printJob.setCopies(1);
+        printJob.setCreationTime(System.currentTimeMillis());
 
         // Track this job so we can forget it when the creator dies.
-        if (!mPrintJobForAppCache.onPrintJobCreated(client.asBinder(), appId,
+        if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId,
                 printJob)) {
             // Not adding a print job means the client is dead - done.
             return null;
@@ -181,12 +187,32 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                mSpooler.createPrintJob(printJob, client, documentAdapter);
+                mSpooler.createPrintJob(printJob);
                 return null;
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
 
-        return printJob;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
+            intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
+            intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
+            intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
+            intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
+
+            IntentSender intentSender = PendingIntent.getActivityAsUser(
+                    mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
+                    | PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId))
+                    .getIntentSender();
+
+            Bundle result = new Bundle();
+            result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
+            result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender);
+
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     public List<PrintJobInfo> getPrintJobInfos(int appId) {
@@ -240,6 +266,10 @@
         if (printJobInfo == null) {
             return;
         }
+
+        // Take a note that we are trying to cancel the job.
+        mSpooler.setPrintJobCancelling(printJobId, true);
+
         if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
             ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName();
             RemotePrintService printService = null;
@@ -770,17 +800,17 @@
             BackgroundThread.getHandler().post(new Runnable() {
                 @Override
                 public void run() {
-                    failActivePrintJobsForServiceInternal(serviceName);
+                    failScheduledPrintJobsForServiceInternal(serviceName);
                 }
             });
         } else {
-            failActivePrintJobsForServiceInternal(serviceName);
+            failScheduledPrintJobsForServiceInternal(serviceName);
         }
     }
 
-    private void failActivePrintJobsForServiceInternal(ComponentName serviceName) {
+    private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) {
         List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName,
-                PrintJobInfo.STATE_ANY_ACTIVE, PrintManager.APP_ID_ANY);
+                PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY);
         if (printJobs == null) {
             return;
         }
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index 86c68f3..0854883 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -930,11 +930,6 @@
         mWifiStateMachine.reconnectCommand();
     }
 
-    public void captivePortalCheckComplete() {
-        enforceConnectivityInternalPermission();
-        mWifiStateMachine.captivePortalCheckComplete();
-    }
-
     /**
      * see {@link android.net.wifi.WifiManager#stopWifi}
      *
diff --git a/services/java/com/android/server/wm/AppTransition.java b/services/java/com/android/server/wm/AppTransition.java
index cd3daaa..756e06a 100644
--- a/services/java/com/android/server/wm/AppTransition.java
+++ b/services/java/com/android/server/wm/AppTransition.java
@@ -722,7 +722,7 @@
     @Override
     public void dump(PrintWriter pw) {
         pw.print(" " + this);
-        pw.print(" mAppTransitionState="); pw.println(appStateToString());
+        pw.print("  mAppTransitionState="); pw.println(appStateToString());
         if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
             pw.print("  mNextAppTransitionType="); pw.println(transitTypeToString());
         }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 0fc10f9..00e659e 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -187,7 +187,7 @@
     static final boolean DEBUG_SURFACE_TRACE = false;
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
-    static final boolean DEBUG_STACK = true;
+    static final boolean DEBUG_STACK = false;
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -1852,13 +1852,21 @@
                     }
                 }
 
-                // Now stick it in.
+                // Now stick it in. For apps over wallpaper keep the wallpaper at the bottommost
+                // layer. For keyguard over wallpaper put the wallpaper under the keyguard.
+                int insertionIndex = 0;
+                if (visible && foundW != null) {
+                    final int type = foundW.mAttrs.type;
+                    if (type == TYPE_KEYGUARD || type == TYPE_KEYGUARD_SCRIM) {
+                        insertionIndex = windows.indexOf(foundW);
+                    }
+                }
                 if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
                     Slog.v(TAG, "Moving wallpaper " + wallpaper
-                            + " from " + oldIndex + " to " + 0);
+                            + " from " + oldIndex + " to " + insertionIndex);
                 }
 
-                windows.add(0, wallpaper);
+                windows.add(insertionIndex, wallpaper);
                 mWindowsChanged = true;
                 changed |= ADJUST_WALLPAPER_LAYERS_CHANGED;
             }
@@ -3014,7 +3022,7 @@
                 TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
 
             inTouchMode = mInTouchMode;
-            animating = mAnimator.mAnimating;
+            animating = mAnimator.mAnimating && win.mWinAnimator.isAnimating();
             if (animating && !mRelayoutWhileAnimating.contains(win)) {
                 mRelayoutWhileAnimating.add(win);
             }
@@ -3386,9 +3394,9 @@
             throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
         }
         Task task = new Task(atoken, stack, userId);
+        mTaskIdToTask.put(taskId, task);
         stack.addTask(task, true);
         stack.getDisplayContent().moveStack(stack, true);
-        mTaskIdToTask.put(taskId, task);
         return task;
     }
 
@@ -4724,23 +4732,6 @@
         return index;
     }
 
-    private void moveHomeTasksLocked(boolean toTop) {
-        final DisplayContent displayContent = getDefaultDisplayContentLocked();
-        if (toTop ^ displayContent.homeOnTop()) {
-            final ArrayList<Task> tasks = displayContent.getHomeStack().getTasks();
-            final int numTasks = tasks.size();
-            for (int i = 0; i < numTasks; ++i) {
-                if (toTop) {
-                    // Keep pulling the bottom task off and moving it to the top.
-                    moveTaskToTop(tasks.get(0).taskId);
-                } else {
-                    // Keep pulling the top task off and moving it to the bottom.
-                    moveTaskToBottom(tasks.get(numTasks - 1).taskId);
-                }
-            }
-        }
-    }
-
     void moveStackWindowsLocked(TaskStack stack) {
         DisplayContent displayContent = stack.getDisplayContent();
 
@@ -4797,15 +4788,9 @@
                 final TaskStack stack = task.mStack;
                 final DisplayContent displayContent = task.getDisplayContent();
                 final boolean isHomeStackTask = stack.isHomeStack();
-                final boolean homeIsOnTop = displayContent.homeOnTop();
-                if (!isHomeStackTask && homeIsOnTop) {
-                    // First move move the home tasks all to the bottom to rearrange the windows.
-                    moveHomeTasksLocked(false);
-                    // Now move the stack itself.
-                    displayContent.moveHomeStackBox(false);
-                } else if (isHomeStackTask && !homeIsOnTop) {
-                    // Move the stack to the top.
-                    displayContent.moveHomeStackBox(true);
+                if (isHomeStackTask != displayContent.homeOnTop()) {
+                    // First move the stack itself.
+                    displayContent.moveHomeStackBox(isHomeStackTask);
                 }
                 stack.moveTaskToTop(task);
                 displayContent.moveStack(stack, true);
@@ -8525,6 +8510,8 @@
             // example, when this transition is being done behind
             // the lock screen.
             if (!mPolicy.allowAppAnimationsLw()) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "Animations disallowed by keyguard or dream.");
                 animLp = null;
             }
 
@@ -8567,8 +8554,7 @@
             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);
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
                 wtoken.mAppAnimator.clearThumbnail();
                 wtoken.inPendingTransaction = false;
                 wtoken.mAppAnimator.animation = null;
@@ -9749,7 +9735,7 @@
                 newFocus = computeFocusedWindowLocked();
             }
 
-            if (true || DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG, "Changing focus from " +
+            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG, "Changing focus from " +
                     mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
             final WindowState oldFocus = mCurrentFocus;
             mCurrentFocus = newFocus;
@@ -10168,6 +10154,12 @@
         }
     }
 
+    public int getInputMethodWindowVisibleHeight() {
+        synchronized (mWindowMap) {
+            return mPolicy.getInputMethodWindowVisibleHeightLw();
+        }
+    }
+
     @Override
     public boolean hasNavigationBar() {
         return mPolicy.hasNavigationBar();
@@ -10417,7 +10409,7 @@
                 }
                 pw.println();
         if (dumpAll) {
-            pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer);
+            pw.print("  mSystemDecorLayer="); pw.print(mSystemDecorLayer);
                     pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
             if (mLastStatusBarVisibility != 0) {
                 pw.print("  mLastStatusBarVisibility=0x");
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index 0354ffd..70fd810 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -825,6 +825,61 @@
         assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
         assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
     }
+
+    /**
+     * Verify that the API cannot cause a run-time reboot by passing in the empty string as an
+     * authority. The problem here is that
+     * {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register
+     * an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}.
+     * This is not strictly a SSE test, but it does depend on the SSE data structures.
+     */
+    @SmallTest
+    public void testExpectedIllegalArguments() throws Exception {
+        try {
+            ContentResolver.setSyncAutomatically(account1, "", true);
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L);
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY);
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.cancelSync(account1, "");
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.setIsSyncable(account1, "", 0);
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.cancelSync(account1, "");
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.requestSync(account1, "", Bundle.EMPTY);
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            ContentResolver.getSyncStatus(account1, "");
+            fail("empty provider string should throw IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        // Make sure we aren't blocking null account/provider for those functions that use it
+        // to specify ALL accounts/providers.
+        ContentResolver.requestSync(null, null, Bundle.EMPTY);
+        ContentResolver.cancelSync(null, null);
+    }
 }
 
 class TestContext extends ContextWrapper {
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 190fea2..c945094 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -331,10 +331,12 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mCdmaDbm);
-        dest.writeInt(mCdmaEcio);
-        dest.writeInt(mEvdoDbm);
-        dest.writeInt(mEvdoEcio);
+        // Need to multiply CdmaDbm, CdmaEcio, EvdoDbm and EvdoEcio by -1
+        // to ensure consistency when reading values written here
+        dest.writeInt(mCdmaDbm * -1);
+        dest.writeInt(mCdmaEcio * -1);
+        dest.writeInt(mEvdoDbm * -1);
+        dest.writeInt(mEvdoEcio * -1);
         dest.writeInt(mEvdoSnr);
     }
 
@@ -343,10 +345,13 @@
      * where the TYPE_LTE token is already been processed.
      */
     private CellSignalStrengthCdma(Parcel in) {
-        mCdmaDbm = in.readInt();
-        mCdmaEcio = in.readInt();
-        mEvdoDbm = in.readInt();
-        mEvdoEcio = in.readInt();
+        // CdmaDbm, CdmaEcio, EvdoDbm and EvdoEcio are written into
+        // the parcel as positive values.
+        // Need to convert into negative values
+        mCdmaDbm = in.readInt() * -1;
+        mCdmaEcio = in.readInt() * -1;
+        mEvdoDbm = in.readInt() * -1;
+        mEvdoEcio = in.readInt() * -1;
         mEvdoSnr = in.readInt();
         if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString());
     }
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index b456bb3..5a1559a 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -247,8 +247,10 @@
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
         dest.writeInt(mSignalStrength);
-        dest.writeInt(mRsrp);
-        dest.writeInt(mRsrq);
+        // Need to multiply rsrp and rsrq by -1
+        // to ensure consistency when reading values written here
+        dest.writeInt(mRsrp * -1);
+        dest.writeInt(mRsrq * -1);
         dest.writeInt(mRssnr);
         dest.writeInt(mCqi);
         dest.writeInt(mTimingAdvance);
@@ -260,8 +262,10 @@
      */
     private CellSignalStrengthLte(Parcel in) {
         mSignalStrength = in.readInt();
-        mRsrp = in.readInt();
-        mRsrq = in.readInt();
+        // rsrp and rsrq are written into the parcel as positive values.
+        // Need to convert into negative values
+        mRsrp = in.readInt() * -1;
+        mRsrq = in.readInt() * -1;
         mRssnr = in.readInt();
         mCqi = in.readInt();
         mTimingAdvance = in.readInt();
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6c4bd66..606fcb4 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -693,7 +693,7 @@
         mOperatorNumeric = m.getString("operator-numeric");
         mIsManualNetworkSelection = m.getBoolean("manual");
         mRilVoiceRadioTechnology = m.getInt("radioTechnology");
-        mRilVoiceRadioTechnology = m.getInt("dataRadioTechnology");
+        mRilDataRadioTechnology = m.getInt("dataRadioTechnology");
         mCssIndicator = m.getBoolean("cssIndicator");
         mNetworkId = m.getInt("networkId");
         mSystemId = m.getInt("systemId");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ea0d220..8f17e72 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -127,19 +127,40 @@
 
     /**
      * The Phone app sends this intent when a user opts to respond-via-message during an incoming
-     * call. By default, the MMS app consumes this message and sends a text message to the caller. A
-     * third party app can provide this functionality in lieu of MMS app by consuming this Intent
-     * and sending the message using their own messaging system.  The intent contains a URI
-     * describing the recipient, and an EXTRA containing the message itself.
-     * <p class="note"><strong>Note:</strong>
-     * The intent-filter which consumes this Intent needs to be in a service which requires the
-     * permission {@link android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE}.</p>
+     * call. By default, the device's default SMS app consumes this message and sends a text message
+     * to the caller. A third party app can also provide this functionality by consuming this Intent
+     * with a {@link android.app.Service} and sending the message using its own messaging system.
+     * <p>The intent contains a URI (available from {@link android.content.Intent#getData})
+     * describing the recipient, using either the {@code sms:}, {@code smsto:}, {@code mms:},
+     * or {@code mmsto:} URI schema. Each of these URI schema carry the recipient information the
+     * same way: the path part of the URI contains the recipient's phone number or a comma-separated
+     * set of phone numbers if there are multiple recipients. For example, {@code
+     * smsto:2065551234}.</p>
      *
-     * <p>
-     * {@link android.content.Intent#getData} is a URI describing the recipient of the message.
-     * <p>
-     * The {@link android.content.Intent#EXTRA_TEXT} extra contains the message
-     * to send.
+     * <p>The intent may also contain extras for the message text (in {@link
+     * android.content.Intent#EXTRA_TEXT}) and a message subject
+     * (in {@link android.content.Intent#EXTRA_SUBJECT}).</p>
+     *
+     * <p class="note"><strong>Note:</strong>
+     * The intent-filter that consumes this Intent needs to be in a {@link android.app.Service}
+     * that requires the
+     * permission {@link android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE}.</p>
+     * <p>For example, the service that receives this intent can be declared in the manifest file
+     * with an intent filter like this:</p>
+     * <pre>
+     * &lt;!-- Service that delivers SMS messages received from the phone "quick response" -->
+     * &lt;service android:name=".HeadlessSmsSendService"
+     *          android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+     *          android:exported="true" >
+     *   &lt;intent-filter>
+     *     &lt;action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+     *     &lt;category android:name="android.intent.category.DEFAULT" />
+     *     &lt;data android:scheme="sms" />
+     *     &lt;data android:scheme="smsto" />
+     *     &lt;data android:scheme="mms" />
+     *     &lt;data android:scheme="mmsto" />
+     *   &lt;/intent-filter>
+     * &lt;/service></pre>
      * <p>
      * Output: nothing.
      */
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 647f014..3e8db06 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -215,4 +215,26 @@
      * Requires system permission.
      */
     void setPremiumSmsPermission(String packageName, int permission);
+
+    /**
+     * SMS over IMS is supported if IMS is registered and SMS is supported
+     * on IMS.
+     *
+     * @return true if SMS over IMS is supported, false otherwise
+     *
+     * @see #getImsSmsFormat()
+     */
+    boolean isImsSmsSupported();
+
+    /**
+     * Gets SMS format supported on IMS.  SMS over IMS format is
+     * either 3GPP or 3GPP2.
+     *
+     * @return android.telephony.SmsMessage.FORMAT_3GPP,
+     *         android.telephony.SmsMessage.FORMAT_3GPP2
+     *      or android.telephony.SmsMessage.FORMAT_UNKNOWN
+     *
+     * @see #isImsSmsSupported()
+     */
+    String getImsSmsFormat();
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 923fef2..821a11c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -263,6 +263,8 @@
     int RIL_REQUEST_GET_CELL_INFO_LIST = 109;
     int RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE = 110;
     int RIL_REQUEST_SET_INITIAL_ATTACH_APN = 111;
+    int RIL_REQUEST_IMS_REGISTRATION_STATE = 112;
+    int RIL_REQUEST_IMS_SEND_SMS = 113;
     int RIL_UNSOL_RESPONSE_BASE = 1000;
     int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
     int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
@@ -301,4 +303,5 @@
     int RIL_UNSOL_RIL_CONNECTED = 1034;
     int RIL_UNSOL_VOICE_RADIO_TECH_CHANGED = 1035;
     int RIL_UNSOL_CELL_INFO_LIST = 1036;
+    int RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED = 1037;
 }
diff --git a/telephony/java/com/android/internal/telephony/SmsConstants.java b/telephony/java/com/android/internal/telephony/SmsConstants.java
index 1ccdc3b..2449108 100644
--- a/telephony/java/com/android/internal/telephony/SmsConstants.java
+++ b/telephony/java/com/android/internal/telephony/SmsConstants.java
@@ -62,6 +62,12 @@
     }
 
     /**
+     * Indicates unknown format SMS message.
+     * @hide pending API council approval
+     */
+    public static final String FORMAT_UNKNOWN = "unknown";
+
+    /**
      * Indicates a 3GPP format SMS message.
      * @hide pending API council approval
      */
diff --git a/tests/BrowserTestPlugin/Android.mk b/tests/BrowserTestPlugin/Android.mk
deleted file mode 100644
index 968d9e6..0000000
--- a/tests/BrowserTestPlugin/Android.mk
+++ /dev/null
@@ -1,36 +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.
-#
-
-TOP_LOCAL_PATH:= $(call my-dir)
-
-# Build application
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := BrowserTestPlugin
-
-LOCAL_JNI_SHARED_LIBRARIES := libtestplugin
-
-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/tests/BrowserTestPlugin/AndroidManifest.xml b/tests/BrowserTestPlugin/AndroidManifest.xml
deleted file mode 100644
index f071ab6..0000000
--- a/tests/BrowserTestPlugin/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.testplugin"
-      android:versionCode="1"
-      android:versionName="1.0">
-
-    <uses-permission android:name="android.webkit.permission.PLUGIN"/>
-
-    <uses-sdk android:minSdkVersion="3" />
-
-    <application android:icon="@drawable/browser_test_plugin"
-                android:label="Browser Test Plugin">
-        <service android:name="TestPlugin">
-            <intent-filter>
-                <action android:name="android.webkit.PLUGIN" />
-            </intent-filter>
-        </service>
-    </application>
-
-</manifest>
diff --git a/tests/BrowserTestPlugin/MODULE_LICENSE_APACHE2 b/tests/BrowserTestPlugin/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/tests/BrowserTestPlugin/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/tests/BrowserTestPlugin/NOTICE b/tests/BrowserTestPlugin/NOTICE
deleted file mode 100644
index 9df2554..0000000
--- a/tests/BrowserTestPlugin/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-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.
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT 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/tests/BrowserTestPlugin/jni/Android.mk b/tests/BrowserTestPlugin/jni/Android.mk
deleted file mode 100644
index d641dad..0000000
--- a/tests/BrowserTestPlugin/jni/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-##
-##
-## Copyright 2009, The Android Open Source Project
-##
-## Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions
-## are met:
-##  * Redistributions of source code must retain the above copyright
-##    notice, this list of conditions and the following disclaimer.
-##  * Redistributions in binary form must reproduce the above copyright
-##    notice, this list of conditions and the following disclaimer in the
-##    documentation and/or other materials provided with the distribution.
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
-## EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-## PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
-## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-## PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
-## OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-##
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-	main.cpp \
-	PluginObject.cpp \
-	event/EventPlugin.cpp \
-
-WEBCORE_PATH := external/webkit/Source/WebCore
-
-LOCAL_C_INCLUDES += \
-	$(LOCAL_PATH) \
-	$(LOCAL_PATH)/event \
-	$(WEBCORE_PATH)/bridge \
-	$(WEBCORE_PATH)/plugins \
-	$(WEBCORE_PATH)/platform/android/JavaVM \
-	external/webkit/Source/WebKit/android/plugins
-
-LOCAL_CFLAGS += -fvisibility=hidden 
-
-
-LOCAL_MODULE := libtestplugin
-LOCAL_MODULE_TAGS := tests
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/BrowserTestPlugin/jni/PluginObject.cpp b/tests/BrowserTestPlugin/jni/PluginObject.cpp
deleted file mode 100644
index 68fca60..0000000
--- a/tests/BrowserTestPlugin/jni/PluginObject.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in
- consideration of your agreement to the following terms, and your use, installation,
- modification or redistribution of this Apple software constitutes acceptance of these
- terms.  If you do not agree with these terms, please do not use, install, modify or
- redistribute this Apple software.
-
- In consideration of your agreement to abide by the following terms, and subject to these
- terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in
- this original Apple software (the "Apple Software"), to use, reproduce, modify and
- redistribute the Apple Software, with or without modifications, in source and/or binary
- forms; provided that if you redistribute the Apple Software in its entirety and without
- modifications, you must retain this notice and the following text and disclaimers in all
- such redistributions of the Apple Software.  Neither the name, trademarks, service marks
- or logos of Apple Computer, Inc. may be used to endorse or promote products derived from
- the Apple Software without specific prior written permission from Apple. Except as expressly
- stated in this notice, no other rights or licenses, express or implied, are granted by Apple
- herein, including but not limited to any patent rights that may be infringed by your
- derivative works or by other works in which the Apple Software may be incorporated.
-
- The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
- EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
- USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
-
- IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-          OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
- REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
- WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
- OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stdlib.h>
-#include "main.h"
-#include "PluginObject.h"
-
-static void pluginInvalidate(NPObject *obj);
-static bool pluginHasProperty(NPObject *obj, NPIdentifier name);
-static bool pluginHasMethod(NPObject *obj, NPIdentifier name);
-static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant);
-static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant);
-static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
-static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result);
-static NPObject *pluginAllocate(NPP npp, NPClass *theClass);
-static void pluginDeallocate(NPObject *obj);
-static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name);
-static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count);
-
-
-
-static NPClass pluginClass = {
-    NP_CLASS_STRUCT_VERSION,
-    pluginAllocate,
-    pluginDeallocate,
-    pluginInvalidate,
-    pluginHasMethod,
-    pluginInvoke,
-    pluginInvokeDefault,
-    pluginHasProperty,
-    pluginGetProperty,
-    pluginSetProperty,
-    pluginRemoveProperty,
-    pluginEnumerate
-};
-
-NPClass *getPluginClass(void)
-{
-    return &pluginClass;
-}
-
-static bool identifiersInitialized = false;
-
-#define ID_TESTFILE_PROPERTY            0
-#define NUM_PROPERTY_IDENTIFIERS        1
-
-static NPIdentifier pluginPropertyIdentifiers[NUM_PROPERTY_IDENTIFIERS];
-static const NPUTF8 *pluginPropertyIdentifierNames[NUM_PROPERTY_IDENTIFIERS] = {
-    "testfile"
-};
-
-#define ID_GETTESTFILE_METHOD                   0
-#define NUM_METHOD_IDENTIFIERS                  1
-
-static NPIdentifier pluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS];
-static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = {
-    "getTestFile"
-};
-
-static void initializeIdentifiers(void)
-{
-    browser->getstringidentifiers(pluginPropertyIdentifierNames, NUM_PROPERTY_IDENTIFIERS, pluginPropertyIdentifiers);
-    browser->getstringidentifiers(pluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, pluginMethodIdentifiers);
-}
-
-static bool pluginHasProperty(NPObject *obj, NPIdentifier name)
-{
-    int i;
-    for (i = 0; i < NUM_PROPERTY_IDENTIFIERS; i++)
-        if (name == pluginPropertyIdentifiers[i])
-            return true;
-    return false;
-}
-
-static bool pluginHasMethod(NPObject *obj, NPIdentifier name)
-{
-    int i;
-    for (i = 0; i < NUM_METHOD_IDENTIFIERS; i++)
-        if (name == pluginMethodIdentifiers[i])
-            return true;
-    return false;
-}
-
-static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant)
-{
-    PluginObject *plugin = (PluginObject *)obj;
-    if (name == pluginPropertyIdentifiers[ID_TESTFILE_PROPERTY]) {
-        BOOLEAN_TO_NPVARIANT(true, *variant);
-        return true;
-    }
-    return false;
-}
-
-static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant)
-{
-    return false;
-}
-
-static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
-{
-    PluginObject *plugin = (PluginObject *)obj;
-    if (name == pluginMethodIdentifiers[ID_GETTESTFILE_METHOD]) {
-        return true;
-    }
-    return false;
-}
-
-static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result)
-{
-    return false;
-}
-
-static void pluginInvalidate(NPObject *obj)
-{
-    // Release any remaining references to JavaScript objects.
-}
-
-static NPObject *pluginAllocate(NPP npp, NPClass *theClass)
-{
-    PluginObject *newInstance = (PluginObject*) malloc(sizeof(PluginObject));
-    newInstance->header._class = theClass;
-    newInstance->header.referenceCount = 1;
-
-    if (!identifiersInitialized) {
-        identifiersInitialized = true;
-        initializeIdentifiers();
-    }
-
-    newInstance->npp = npp;
-
-    return &newInstance->header;
-}
-
-static void pluginDeallocate(NPObject *obj)
-{
-    free(obj);
-}
-
-static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name)
-{
-    return false;
-}
-
-static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count)
-{
-    return false;
-}
diff --git a/tests/BrowserTestPlugin/jni/PluginObject.h b/tests/BrowserTestPlugin/jni/PluginObject.h
deleted file mode 100644
index 037367e..0000000
--- a/tests/BrowserTestPlugin/jni/PluginObject.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in
- consideration of your agreement to the following terms, and your use, installation,
- modification or redistribution of this Apple software constitutes acceptance of these
- terms.  If you do not agree with these terms, please do not use, install, modify or
- redistribute this Apple software.
-
- In consideration of your agreement to abide by the following terms, and subject to these
- terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in
- this original Apple software (the "Apple Software"), to use, reproduce, modify and
- redistribute the Apple Software, with or without modifications, in source and/or binary
- forms; provided that if you redistribute the Apple Software in its entirety and without
- modifications, you must retain this notice and the following text and disclaimers in all
- such redistributions of the Apple Software.  Neither the name, trademarks, service marks
- or logos of Apple Computer, Inc. may be used to endorse or promote products derived from
- the Apple Software without specific prior written permission from Apple. Except as expressly
- stated in this notice, no other rights or licenses, express or implied, are granted by Apple
- herein, including but not limited to any patent rights that may be infringed by your
- derivative works or by other works in which the Apple Software may be incorporated.
-
- The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
- EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
- USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
-
- IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-          OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
- REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
- WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
- OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef PluginObject__DEFINED
-#define PluginObject__DEFINED
-
-#include "main.h"
-
-class SubPlugin {
-public:
-    SubPlugin(NPP inst) : m_inst(inst) {}
-    virtual ~SubPlugin() {}
-    virtual int16_t handleEvent(const ANPEvent* evt) = 0;
-
-    NPP inst() const { return m_inst; }
-
-private:
-    NPP m_inst;
-};
-
-typedef struct PluginObject {
-    NPObject header;
-    NPP npp;
-    NPWindow* window;
-
-    SubPlugin* subPlugin;
-
-} PluginObject;
-
-NPClass *getPluginClass(void);
-
-#endif // PluginObject__DEFINED
diff --git a/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp b/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp
deleted file mode 100644
index 91f1b3d..0000000
--- a/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "EventPlugin.h"
-#include "android_npapi.h"
-
-#include <stdio.h>
-#include <sys/time.h>
-#include <time.h>
-#include <math.h>
-#include <string.h>
-
-extern NPNetscapeFuncs*        browser;
-extern ANPCanvasInterfaceV0    gCanvasI;
-extern ANPLogInterfaceV0       gLogI;
-extern ANPPaintInterfaceV0     gPaintI;
-extern ANPTypefaceInterfaceV0  gTypefaceI;
-
-///////////////////////////////////////////////////////////////////////////////
-
-EventPlugin::EventPlugin(NPP inst) : SubPlugin(inst) { }
-
-EventPlugin::~EventPlugin() { }
-
-void EventPlugin::drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip) {
-
-    gLogI.log(kDebug_ANPLogType, " ------ %p drawing the plugin (%d,%d)",
-              inst(), bitmap.width, bitmap.height);
-
-    // get the plugin's dimensions according to the DOM
-    PluginObject *obj = (PluginObject*) inst()->pdata;
-    const int W = obj->window->width;
-    const int H = obj->window->height;
-
-    // compute the current zoom level
-    const float zoomFactorW = static_cast<float>(bitmap.width) / W;
-    const float zoomFactorH = static_cast<float>(bitmap.height) / H;
-
-    // check to make sure the zoom level is uniform
-    if (zoomFactorW + .01 < zoomFactorH && zoomFactorW - .01 > zoomFactorH)
-        gLogI.log(kError_ANPLogType, " ------ %p zoom is out of sync (%f,%f)",
-                  inst(), zoomFactorW, zoomFactorH);
-
-    // scale the variables based on the zoom level
-    const int fontSize = (int)(zoomFactorW * 16);
-    const int leftMargin = (int)(zoomFactorW * 10);
-
-    // create and clip a canvas
-    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
-
-    ANPRectF clipR;
-    clipR.left = clip.left;
-    clipR.top = clip.top;
-    clipR.right = clip.right;
-    clipR.bottom = clip.bottom;
-    gCanvasI.clipRect(canvas, &clipR);
-
-    gCanvasI.drawColor(canvas, 0xFFFFFFFF);
-
-    // configure the paint
-    ANPPaint* paint = gPaintI.newPaint();
-    gPaintI.setFlags(paint, gPaintI.getFlags(paint) | kAntiAlias_ANPPaintFlag);
-    gPaintI.setColor(paint, 0xFF0000FF);
-    gPaintI.setTextSize(paint, fontSize);
-
-    // configure the font
-    ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
-    gPaintI.setTypeface(paint, tf);
-    gTypefaceI.unref(tf);
-
-    // retrieve the font metrics
-    ANPFontMetrics fm;
-    gPaintI.getFontMetrics(paint, &fm);
-
-    // write text on the canvas
-    const char c[] = "Browser Test Plugin";
-    gCanvasI.drawText(canvas, c, sizeof(c)-1, leftMargin, -fm.fTop, paint);
-
-    // clean up variables
-    gPaintI.deletePaint(paint);
-    gCanvasI.deleteCanvas(canvas);
-}
-
-void EventPlugin::printToDiv(const char* text, int length) {
-    // Get the plugin's DOM object
-    NPObject* windowObject = NULL;
-    browser->getvalue(inst(), NPNVWindowNPObject, &windowObject);
-
-    if (!windowObject)
-        gLogI.log(kError_ANPLogType, " ------ %p Unable to retrieve DOM Window", inst());
-
-    // create a string (JS code) that is stored in memory allocated by the browser
-    const char* jsBegin = "var outputDiv = document.getElementById('eventOutput'); outputDiv.innerHTML += ' ";
-    const char* jsEnd = "';";
-
-    // allocate memory and configure pointers
-    int totalLength = strlen(jsBegin) + length + strlen(jsEnd);
-    char* beginMem = (char*)browser->memalloc(totalLength);
-    char* middleMem = beginMem + strlen(jsBegin);
-    char* endMem = middleMem + length;
-
-    // copy into the allocated memory
-    memcpy(beginMem, jsBegin, strlen(jsBegin));
-    memcpy(middleMem, text, length);
-    memcpy(endMem, jsEnd, strlen(jsEnd));
-
-    gLogI.log(kDebug_ANPLogType, "text: %.*s\n", totalLength, (char*)beginMem);
-
-    // execute the javascript in the plugin's DOM object
-    NPString script = { (char*)beginMem, totalLength };
-    NPVariant scriptVariant;
-    if (!browser->evaluate(inst(), windowObject, &script, &scriptVariant))
-        gLogI.log(kError_ANPLogType, " ------ %p Unable to eval the JS.", inst());
-
-    // free the memory allocated within the browser
-    browser->memfree(beginMem);
-}
-
-int16_t EventPlugin::handleEvent(const ANPEvent* evt) {
-    switch (evt->eventType) {
-
-        case kDraw_ANPEventType: {
-            switch (evt->data.draw.model) {
-                case kBitmap_ANPDrawingModel:
-                    drawPlugin(evt->data.draw.data.bitmap, evt->data.draw.clip);
-                    return 1;
-                default:
-                    break;   // unknown drawing model
-            }
-        }
-        case kLifecycle_ANPEventType:
-            switch (evt->data.lifecycle.action) {
-                case kOnLoad_ANPLifecycleAction: {
-                    char msg[] = "lifecycle-onLoad";
-                    printToDiv(msg, strlen(msg));
-                    break;
-                }
-                case kGainFocus_ANPLifecycleAction: {
-                    char msg[] = "lifecycle-gainFocus";
-                    printToDiv(msg, strlen(msg));
-                    break;
-                }
-                case kLoseFocus_ANPLifecycleAction: {
-                    char msg[] = "lifecycle-loseFocus";
-                    printToDiv(msg, strlen(msg));
-                    break;
-                }
-            }
-            return 1;
-        case kTouch_ANPEventType:
-            gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request touch events", inst());
-            break;
-        case kKey_ANPEventType:
-            gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request key events", inst());
-            break;
-        default:
-            break;
-    }
-    return 0;   // unknown or unhandled event
-}
diff --git a/tests/BrowserTestPlugin/jni/event/EventPlugin.h b/tests/BrowserTestPlugin/jni/event/EventPlugin.h
deleted file mode 100644
index 043be85..0000000
--- a/tests/BrowserTestPlugin/jni/event/EventPlugin.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "PluginObject.h"
-
-#ifndef eventPlugin__DEFINED
-#define eventPlugin__DEFINED
-
-class EventPlugin : public SubPlugin {
-public:
-    EventPlugin(NPP inst);
-    virtual ~EventPlugin();
-    virtual int16_t handleEvent(const ANPEvent* evt);
-
-private:
-    void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
-    void printToDiv(const char* text, int length);
-};
-
-#endif // eventPlugin__DEFINED
diff --git a/tests/BrowserTestPlugin/jni/main.cpp b/tests/BrowserTestPlugin/jni/main.cpp
deleted file mode 100644
index 511180c..0000000
--- a/tests/BrowserTestPlugin/jni/main.cpp
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include "android_npapi.h"
-#include "main.h"
-#include "PluginObject.h"
-#include "EventPlugin.h"
-
-NPNetscapeFuncs* browser;
-#define EXPORT __attribute__((visibility("default")))
-
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
-        char* argn[], char* argv[], NPSavedData* saved);
-NPError NPP_Destroy(NPP instance, NPSavedData** save);
-NPError NPP_SetWindow(NPP instance, NPWindow* window);
-NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream,
-        NPBool seekable, uint16_t* stype);
-NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
-int32_t   NPP_WriteReady(NPP instance, NPStream* stream);
-int32_t   NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len,
-        void* buffer);
-void    NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
-void    NPP_Print(NPP instance, NPPrint* platformPrint);
-int16_t   NPP_HandleEvent(NPP instance, void* event);
-void    NPP_URLNotify(NPP instance, const char* URL, NPReason reason,
-        void* notifyData);
-NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
-NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
-
-extern "C" {
-EXPORT NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env);
-EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value);
-EXPORT const char* NP_GetMIMEDescription(void);
-EXPORT void NP_Shutdown(void);
-};
-
-ANPAudioTrackInterfaceV0    gSoundI;
-ANPBitmapInterfaceV0        gBitmapI;
-ANPCanvasInterfaceV0        gCanvasI;
-ANPLogInterfaceV0           gLogI;
-ANPPaintInterfaceV0         gPaintI;
-ANPPathInterfaceV0          gPathI;
-ANPTypefaceInterfaceV0      gTypefaceI;
-ANPWindowInterfaceV0        gWindowI;
-
-#define ARRAY_COUNT(array)      (sizeof(array) / sizeof(array[0]))
-
-NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env)
-{
-    // Make sure we have a function table equal or larger than we are built against.
-    if (browserFuncs->size < sizeof(NPNetscapeFuncs)) {
-        return NPERR_GENERIC_ERROR;
-    }
-
-    // Copy the function table (structure)
-    browser = (NPNetscapeFuncs*) malloc(sizeof(NPNetscapeFuncs));
-    memcpy(browser, browserFuncs, sizeof(NPNetscapeFuncs));
-
-    // Build the plugin function table
-    pluginFuncs->version = 11;
-    pluginFuncs->size = sizeof(pluginFuncs);
-    pluginFuncs->newp = NPP_New;
-    pluginFuncs->destroy = NPP_Destroy;
-    pluginFuncs->setwindow = NPP_SetWindow;
-    pluginFuncs->newstream = NPP_NewStream;
-    pluginFuncs->destroystream = NPP_DestroyStream;
-    pluginFuncs->asfile = NPP_StreamAsFile;
-    pluginFuncs->writeready = NPP_WriteReady;
-    pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
-    pluginFuncs->print = NPP_Print;
-    pluginFuncs->event = NPP_HandleEvent;
-    pluginFuncs->urlnotify = NPP_URLNotify;
-    pluginFuncs->getvalue = NPP_GetValue;
-    pluginFuncs->setvalue = NPP_SetValue;
-
-    static const struct {
-        NPNVariable     v;
-        uint32_t        size;
-        ANPInterface*   i;
-    } gPairs[] = {
-        { kCanvasInterfaceV0_ANPGetValue,       sizeof(gCanvasI),   &gCanvasI },
-        { kLogInterfaceV0_ANPGetValue,          sizeof(gLogI),      &gLogI },
-        { kPaintInterfaceV0_ANPGetValue,        sizeof(gPaintI),    &gPaintI },
-        { kTypefaceInterfaceV0_ANPGetValue,     sizeof(gTypefaceI), &gTypefaceI },
-    };
-    for (size_t i = 0; i < ARRAY_COUNT(gPairs); i++) {
-        gPairs[i].i->inSize = gPairs[i].size;
-        NPError err = browser->getvalue(NULL, gPairs[i].v, gPairs[i].i);
-        if (err) {
-            return err;
-        }
-    }
-
-    return NPERR_NO_ERROR;
-}
-
-void NP_Shutdown(void)
-{
-
-}
-
-const char *NP_GetMIMEDescription(void)
-{
-    return "application/x-browsertestplugin:btp:Android Browser Test Plugin";
-}
-
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
-                char* argn[], char* argv[], NPSavedData* saved)
-{
-
-
-    gLogI.log(kDebug_ANPLogType, "creating plugin");
-
-    PluginObject *obj = NULL;
-
-    // Scripting functions appeared in NPAPI version 14
-    if (browser->version >= 14) {
-    instance->pdata = browser->createobject (instance, getPluginClass());
-    obj = static_cast<PluginObject*>(instance->pdata);
-    memset(obj, 0, sizeof(*obj));
-    } else {
-        return NPERR_GENERIC_ERROR;
-    }
-
-    // select the drawing model
-    ANPDrawingModel model = kBitmap_ANPDrawingModel;
-
-    // notify the plugin API of the drawing model we wish to use. This must be
-    // done prior to creating certain subPlugin objects (e.g. surfaceViews)
-    NPError err = browser->setvalue(instance, kRequestDrawingModel_ANPSetValue,
-                            reinterpret_cast<void*>(model));
-    if (err) {
-        gLogI.log(kError_ANPLogType, "request model %d err %d", model, err);
-        return err;
-    }
-
-    // create the sub-plugin
-    obj->subPlugin = new EventPlugin(instance);
-
-    return NPERR_NO_ERROR;
-}
-
-NPError NPP_Destroy(NPP instance, NPSavedData** save)
-{
-    PluginObject *obj = (PluginObject*) instance->pdata;
-    if (obj) {
-        delete obj->subPlugin;
-        browser->releaseobject(&obj->header);
-    }
-
-    return NPERR_NO_ERROR;
-}
-
-NPError NPP_SetWindow(NPP instance, NPWindow* window)
-{
-    PluginObject *obj = (PluginObject*) instance->pdata;
-
-    // Do nothing if browser didn't support NPN_CreateObject which would have created the PluginObject.
-    if (obj != NULL) {
-        obj->window = window;
-    }
-
-    return NPERR_NO_ERROR;
-}
-
-NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
-{
-    *stype = NP_ASFILEONLY;
-    return NPERR_NO_ERROR;
-}
-
-NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
-{
-    return NPERR_NO_ERROR;
-}
-
-int32_t NPP_WriteReady(NPP instance, NPStream* stream)
-{
-    return 0;
-}
-
-int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
-{
-    return 0;
-}
-
-void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
-{
-}
-
-void NPP_Print(NPP instance, NPPrint* platformPrint)
-{
-}
-
-int16_t NPP_HandleEvent(NPP instance, void* event)
-{
-    PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
-    const ANPEvent* evt = reinterpret_cast<const ANPEvent*>(event);
-
-    if(!obj->subPlugin) {
-        gLogI.log(kError_ANPLogType, "the sub-plugin is null.");
-        return 0; // unknown or unhandled event
-    }
-    else {
-        return obj->subPlugin->handleEvent(evt);
-    }
-}
-
-void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
-{
-}
-
-EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value) {
-
-    if (variable == NPPVpluginNameString) {
-        const char **str = (const char **)value;
-        *str = "Browser Test Plugin";
-        return NPERR_NO_ERROR;
-    }
-
-    if (variable == NPPVpluginDescriptionString) {
-        const char **str = (const char **)value;
-        *str = "Description of Browser Test Plugin";
-        return NPERR_NO_ERROR;
-    }
-
-    return NPERR_GENERIC_ERROR;
-}
-
-NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
-{
-    if (variable == NPPVpluginScriptableNPObject) {
-        void **v = (void **)value;
-        PluginObject *obj = (PluginObject*) instance->pdata;
-
-        if (obj)
-            browser->retainobject((NPObject*)obj);
-
-        *v = obj;
-        return NPERR_NO_ERROR;
-    }
-
-    return NPERR_GENERIC_ERROR;
-}
-
-NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
-{
-    return NPERR_GENERIC_ERROR;
-}
-
diff --git a/tests/BrowserTestPlugin/jni/main.h b/tests/BrowserTestPlugin/jni/main.h
deleted file mode 100644
index e6e8c73..0000000
--- a/tests/BrowserTestPlugin/jni/main.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *  * Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *  * Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <npapi.h>
-#include <npfunctions.h>
-#include <npruntime.h>
-#include "android_npapi.h"
-
-extern NPNetscapeFuncs* browser;
diff --git a/tests/BrowserTestPlugin/res/drawable/browser_test_plugin.png b/tests/BrowserTestPlugin/res/drawable/browser_test_plugin.png
deleted file mode 100644
index 47c79d1..0000000
--- a/tests/BrowserTestPlugin/res/drawable/browser_test_plugin.png
+++ /dev/null
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/Android.mk b/tests/Camera2Tests/SmartCamera/Android.mk
new file mode 100644
index 0000000..3fa8f54
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/Android.mk
@@ -0,0 +1,14 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 $(call all-subdir-makefiles)
diff --git a/tests/Camera2Tests/SmartCamera/README.txt b/tests/Camera2Tests/SmartCamera/README.txt
new file mode 100644
index 0000000..1fff3ab8
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/README.txt
@@ -0,0 +1,60 @@
+Copyright 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+Smart Camera / Auto Snapshot (formerly named SimpleCamera) ReadMe
+
+Created by: Benjamin W Hendricks
+
+How to build the application:
+From root: make SmartCamera will build the apk for generic
+Otherwise, to build the application for a specific device, lunch to that device
+and then run mm while in the SimpleCamera directory.
+Then take the given Install path (out/target/.../SmartCamera.apk)
+and run adb install out/target/.../SmartCamera.apk. The application should
+then appear in the launcher of your device.
+You might also need to run adb sync after building to sync the
+libsmartcamera_jni library
+Summarized:
+    make SmartCamera
+    adb remount
+    adb sync
+    adb install -r $ANDROID_PRODUCT_OUT/data/app/SmartCamera.apk
+
+How to run the application:
+On a Nexus 7, open up the application from the launcher, and the camera preview
+should appear. From there, you can go to the gallery with the gallery button or
+press start to start capturing images. You can also change the number of images
+to be captured by changing the number on the spinner (between 1-10).
+
+What does it do:
+The application tries to take good pictures for you automatically when in the
+start mode. On stop, the application will capture whatever images are in the
+bottom preview and save them to the Gallery. It does this by looking at the
+following image features:
+    - Sharpness
+    - Brightness
+    - Motion of the device
+    - Colorfulness
+    - Contrast
+    - Exposure (over/under)
+
+By comparing each of these features frame by frame, a score is calculated to
+determine whether an image is better or worse than the previous few frames,
+and from that score I can determine the great images from the bad ones.
+
+What libraries does it use:
+- Mobile Filter Framework (MFF)
+- Camera2 API
+- Renderscript
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/.classpath b/tests/Camera2Tests/SmartCamera/SimpleCamera/.classpath
new file mode 100644
index 0000000..3f9691c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/.project b/tests/Camera2Tests/SmartCamera/SimpleCamera/.project
new file mode 100644
index 0000000..2517e2d
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>CameraShoot</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk
new file mode 100644
index 0000000..801c81c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+ifneq ($(TARGET_BUILD_JAVA_SUPPORT_LEVEL),)
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+# comment it out for now since we need use some hidden APIs
+# LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-renderscript-files-under, src)
+
+LOCAL_PACKAGE_NAME := SmartCamera
+LOCAL_JNI_SHARED_LIBRARIES := libsmartcamera_jni
+
+include $(BUILD_PACKAGE)
+
+# Include packages in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/AndroidManifest.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/AndroidManifest.xml
new file mode 100644
index 0000000..0681868
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="androidx.media.filterfw.samples.simplecamera">
+    <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="19"/>
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <application android:label="Smart Camera"
+                  android:debuggable="true">
+    <uses-library android:name="com.google.android.media.effects"
+                  android:required="false" />
+
+        <activity android:name=".SmartCamera"
+                  android:label="Smart Camera"
+                  android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+</manifest>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/0002_000390.jpg b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/0002_000390.jpg
new file mode 100644
index 0000000..9b4bce4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/0002_000390.jpg
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLeyesclosed_100.emd b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLeyesclosed_100.emd
new file mode 100644
index 0000000..8c3d811
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLeyesclosed_100.emd
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLjoy_100.emd b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLjoy_100.emd
new file mode 100644
index 0000000..4ae3fbd
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/assets/frsdk_expression_modules/BCLjoy_100.emd
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/ic_launcher-web.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/ic_launcher-web.png
new file mode 100644
index 0000000..f142216
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/ic_launcher-web.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk
new file mode 100644
index 0000000..616a11b
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FILTERFW_NATIVE_PATH := $(call my-dir)
+
+
+#
+# Build module libfilterframework
+#
+LOCAL_PATH := $(FILTERFW_NATIVE_PATH)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := 14
+
+LOCAL_MODULE := libsmartcamera_jni
+
+LOCAL_SRC_FILES := contrast.cpp \
+                brightness.cpp \
+                exposure.cpp \
+                colorspace.cpp \
+                histogram.cpp \
+                frametovalues.cpp \
+                pixelutils.cpp \
+                sobeloperator.cpp \
+                stats_scorer.cpp
+
+LOCAL_STATIC_LIBRARIES += \
+    libcutils
+
+LOCAL_C_INCLUDES += \
+    system/core/include \
+
+LOCAL_NDK_STL_VARIANT := stlport_static
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Application.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Application.mk
new file mode 100644
index 0000000..2b93b3c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Application.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+APP_STL := stlport_static
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.cpp
new file mode 100644
index 0000000..998fd4c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract brightness from image (handed down as ByteBuffer).
+
+#include "brightness.h"
+
+#include <math.h>
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+jfloat
+Java_androidx_media_filterfw_samples_simplecamera_AvgBrightnessFilter_brightnessOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer) {
+
+    if (imageBuffer == 0) {
+        return 0.0f;
+    }
+    float pixelTotals[] = { 0.0f, 0.0f, 0.0f };
+    const int numPixels = width * height;
+    unsigned char* srcPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    for (int i = 0; i < numPixels; i++) {
+        pixelTotals[0] += *(srcPtr + 4 * i);
+        pixelTotals[1] += *(srcPtr + 4 * i + 1);
+        pixelTotals[2] += *(srcPtr + 4 * i + 2);
+    }
+    float avgPixels[] = { 0.0f, 0.0f, 0.0f };
+
+    avgPixels[0] = pixelTotals[0] / numPixels;
+    avgPixels[1] = pixelTotals[1] / numPixels;
+    avgPixels[2] = pixelTotals[2] / numPixels;
+    float returnValue = sqrt(0.241f * avgPixels[0] * avgPixels[0] +
+                            0.691f * avgPixels[1] * avgPixels[1] +
+                            0.068f * avgPixels[2] * avgPixels[2]);
+
+    return returnValue / 255;
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.h
new file mode 100644
index 0000000..c09e3b5
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/brightness.h
@@ -0,0 +1,36 @@
+/* Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract brightness from image (handed down as ByteBuffer).
+
+#ifndef ANDROID_FILTERFW_JNI_BRIGHTNESS_H
+#define ANDROID_FILTERFW_JNI_BRIGHTNESS_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    JNIEXPORT jfloat JNICALL
+    Java_androidx_media_filterfw_samples_simplecamera_AvgBrightnessFilter_brightnessOperator(
+        JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_BRIGHTNESS_H
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.cpp
new file mode 100644
index 0000000..63e2ebf
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "colorspace.h"
+
+#include <jni.h>
+#include <stdint.h>
+
+typedef uint8_t uint8;
+typedef uint32_t uint32;
+typedef int32_t int32;
+
+// RGBA helper struct allows access as int and individual channels
+// WARNING: int value depends on endianness and should not be used to analyze individual channels.
+union Rgba {
+  uint32 color;
+  uint8 channel[4];
+};
+
+// Channel index constants
+static const uint8 kRed = 0;
+static const uint8 kGreen = 1;
+static const uint8 kBlue = 2;
+static const uint8 kAlpha = 3;
+
+// Clamp to range 0-255
+static inline uint32 clamp(int32 x) {
+  return x > 255 ? 255 : (x < 0 ? 0 : x);
+}
+
+// Convert YUV to RGBA
+// This uses the ITU-R BT.601 coefficients.
+static inline Rgba convertYuvToRgba(int32 y, int32 u, int32 v) {
+  Rgba color;
+  color.channel[kRed] = clamp(y + static_cast<int>(1.402 * v));
+  color.channel[kGreen] = clamp(y - static_cast<int>(0.344 * u + 0.714 * v));
+  color.channel[kBlue] = clamp(y + static_cast<int>(1.772 * u));
+  color.channel[kAlpha] = 0xFF;
+  return color;
+}
+
+// Colorspace conversion functions /////////////////////////////////////////////////////////////////
+void JNI_COLORSPACE_METHOD(nativeYuv420pToRgba8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height) {
+  uint8* const pInput = static_cast<uint8*>(env->GetDirectBufferAddress(input));
+  Rgba* const pOutput = static_cast<Rgba*>(env->GetDirectBufferAddress(output));
+
+  const int size = width * height;
+
+  uint8* pInY = pInput;
+  uint8* pInU = pInput + size;
+  uint8* pInV = pInput + size + size / 4;
+  Rgba* pOutColor = pOutput;
+
+  const int u_offset = size;
+  const int v_offset = u_offset + size / 4;
+
+  for (int y = 0; y < height; y += 2) {
+    for (int x = 0; x < width; x += 2) {
+      int u, v, y1, y2, y3, y4;
+
+      y1 = pInY[0];
+      y2 = pInY[1];
+      y3 = pInY[width];
+      y4 = pInY[width + 1];
+
+      u = *pInU - 128;
+      v = *pInV - 128;
+
+      pOutColor[0] = convertYuvToRgba(y1, u, v);
+      pOutColor[1] = convertYuvToRgba(y2, u, v);
+      pOutColor[width] = convertYuvToRgba(y3, u, v);
+      pOutColor[width + 1] = convertYuvToRgba(y4, u, v);
+
+      pInY += 2;
+      pInU++;
+      pInV++;
+      pOutColor += 2;
+    }
+    pInY += width;
+    pOutColor += width;
+  }
+}
+
+void JNI_COLORSPACE_METHOD(nativeArgb8888ToRgba8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height) {
+  Rgba* pInput = static_cast<Rgba*>(env->GetDirectBufferAddress(input));
+  Rgba* pOutput = static_cast<Rgba*>(env->GetDirectBufferAddress(output));
+
+  for (int i = 0; i < width * height; ++i) {
+    Rgba color_in = *pInput++;
+    Rgba& color_out = *pOutput++;
+    color_out.channel[kRed] = color_in.channel[kGreen];
+    color_out.channel[kGreen] = color_in.channel[kBlue];
+    color_out.channel[kBlue] = color_in.channel[kAlpha];
+    color_out.channel[kAlpha] = color_in.channel[kRed];
+  }
+}
+
+void JNI_COLORSPACE_METHOD(nativeRgba8888ToHsva8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height) {
+  Rgba* pInput = static_cast<Rgba*>(env->GetDirectBufferAddress(input));
+  Rgba* pOutput = static_cast<Rgba*>(env->GetDirectBufferAddress(output));
+
+  int r, g, b, a, h, s, v, c_max, c_min;
+  float delta;
+  for (int i = 0; i < width * height; ++i) {
+    Rgba color_in = *pInput++;
+    Rgba& color_out = *pOutput++;
+    r = color_in.channel[kRed];
+    g = color_in.channel[kGreen];
+    b = color_in.channel[kBlue];
+    a = color_in.channel[kAlpha];
+
+    if (r > g) {
+      c_min = (g > b) ? b : g;
+      c_max = (r > b) ? r : b;
+    } else {
+      c_min = (r > b) ? b : r;
+      c_max = (g > b) ? g : b;
+    }
+    delta = c_max -c_min;
+
+    float scaler = 255 * 60 / 360.0f;
+    if (c_max == r) {
+      h = (g > b) ? static_cast<int>(scaler * (g - b) / delta) :
+          static_cast<int>(scaler * ((g - b) / delta + 6));
+    } else if (c_max == g) {
+      h = static_cast<int>(scaler * ((b - r) / delta + 2));
+    } else {  // Cmax == b
+      h = static_cast<int>(scaler * ((r - g) / delta + 4));
+    }
+    s = (delta == 0.0f) ? 0 : static_cast<unsigned char>(delta / c_max * 255);
+    v = c_max;
+
+    color_out.channel[kRed] = h;
+    color_out.channel[kGreen] = s;
+    color_out.channel[kBlue] = v;
+    color_out.channel[kAlpha] = a;
+  }
+}
+
+void JNI_COLORSPACE_METHOD(nativeRgba8888ToYcbcra8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height) {
+  Rgba* pInput = static_cast<Rgba*>(env->GetDirectBufferAddress(input));
+  Rgba* pOutput = static_cast<Rgba*>(env->GetDirectBufferAddress(output));
+
+  int r, g, b;
+  for (int i = 0; i < width * height; ++i) {
+    Rgba color_in = *pInput++;
+    Rgba& color_out = *pOutput++;
+    r = color_in.channel[kRed];
+    g = color_in.channel[kGreen];
+    b = color_in.channel[kBlue];
+
+    color_out.channel[kRed] =
+        static_cast<unsigned char>((65.738 * r + 129.057 * g + 25.064 * b) / 256 + 16);
+    color_out.channel[kGreen] =
+        static_cast<unsigned char>((-37.945 * r - 74.494 * g + 112.439 * b) / 256 + 128);
+    color_out.channel[kBlue] =
+        static_cast<unsigned char>((112.439 * r - 94.154 * g - 18.285 * b) / 256 + 128);
+    color_out.channel[kAlpha] = color_in.channel[kAlpha];
+  }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.h
new file mode 100644
index 0000000..c332749
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/colorspace.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_COLORSPACE_H
+#define ANDROID_FILTERFW_JNI_COLORSPACE_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define JNI_COLORSPACE_METHOD(METHOD_NAME) \
+  Java_androidx_media_filterfw_ColorSpace_ ## METHOD_NAME
+
+JNIEXPORT void JNICALL
+JNI_COLORSPACE_METHOD(nativeYuv420pToRgba8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height);
+
+JNIEXPORT void JNICALL
+JNI_COLORSPACE_METHOD(nativeArgb8888ToRgba8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height);
+
+JNIEXPORT void JNICALL
+JNI_COLORSPACE_METHOD(nativeRgba8888ToHsva8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height);
+
+JNIEXPORT void JNICALL
+JNI_COLORSPACE_METHOD(nativeRgba8888ToYcbcra8888)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_COLORSPACE_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.cpp
new file mode 100644
index 0000000..222f738
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract contrast ratio from image (handed down as ByteBuffer).
+
+#include "contrast.h"
+
+#include <math.h>
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+jfloat
+Java_androidx_media_filterfw_samples_simplecamera_ContrastRatioFilter_contrastOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer) {
+
+    if (imageBuffer == 0) {
+      return 0.0f;
+    }
+    float total = 0;
+    const int numPixels = width * height;
+    unsigned char* srcPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    float* lumArray = new float[numPixels];
+    for (int i = 0; i < numPixels; i++) {
+        lumArray[i] = (0.2126f * *(srcPtr + 4 * i) + 0.7152f *
+            *(srcPtr + 4 * i + 1) + 0.0722f * *(srcPtr + 4 * i + 2)) / 255;
+        total += lumArray[i];
+    }
+    const float avg = total / numPixels;
+    float sum = 0;
+
+    for (int i = 0; i < numPixels; i++) {
+        sum += (lumArray[i] - avg) * (lumArray[i] - avg);
+    }
+    delete[] lumArray;
+    return ((float) sqrt(sum / numPixels));
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.h
new file mode 100644
index 0000000..ddcd3d4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/contrast.h
@@ -0,0 +1,36 @@
+/* Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract contrast from image (handed down as ByteBuffer).
+
+#ifndef ANDROID_FILTERFW_JNI_CONTRAST_H
+#define ANDROID_FILTERFW_JNI_CONTRAST_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    JNIEXPORT jfloat JNICALL
+    Java_androidx_media_filterfw_samples_simplecamera_ContrastRatioFilter_contrastOperator(
+        JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_CONTRAST_H
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.cpp
new file mode 100644
index 0000000..b2853f7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract exposure from image (handed down as ByteBuffer).
+
+#include "exposure.h"
+
+#include <math.h>
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+
+jfloat
+Java_androidx_media_filterfw_samples_simplecamera_ExposureFilter_overExposureOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer) {
+    if (imageBuffer == 0) {
+        return 0.0f;
+    }
+    const int numPixels = width * height;
+    unsigned char* srcPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    int output = 0;
+    float tempLuminance = 0.0f;
+
+    for (int i = 0; i < numPixels; i++) {
+        tempLuminance = (0.2126f * *(srcPtr + 4 * i) +
+                        0.7152f * *(srcPtr + 4 * i + 1) +
+                        0.0722f * *(srcPtr + 4 * i + 2));
+        if (tempLuminance + 5 >= 255) {
+            output++;
+        }
+    }
+    return (static_cast<float>(output)) / numPixels;
+}
+
+jfloat
+Java_androidx_media_filterfw_samples_simplecamera_ExposureFilter_underExposureOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer) {
+    if (imageBuffer == 0) {
+        return 0.0f;
+    }
+    const int numPixels = width * height;
+    unsigned char* srcPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    int output = 0;
+    float tempLuminance = 0.0f;
+
+    for (int i = 0; i < numPixels; i++) {
+        tempLuminance = (0.2126f * *(srcPtr + 4 * i) +
+                        0.7152f * *(srcPtr + 4 * i + 1) +
+                        0.0722f * *(srcPtr + 4 * i + 2));
+        if (tempLuminance - 5 <= 0) {
+            output++;
+        }
+    }
+    return (static_cast<float>(output)) / numPixels;
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.h
new file mode 100644
index 0000000..bc6e3b1
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/exposure.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Native function to extract exposure from image (handed down as ByteBuffer).
+
+#ifndef ANDROID_FILTERFW_JNI_EXPOSURE_H
+#define ANDROID_FILTERFW_JNI_EXPOSURE_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    JNIEXPORT jfloat JNICALL
+    Java_androidx_media_filterfw_samples_simplecamera_ExposureFilter_underExposureOperator(
+        JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer);
+
+    JNIEXPORT jfloat JNICALL
+    Java_androidx_media_filterfw_samples_simplecamera_ExposureFilter_overExposureOperator(
+        JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_EXPOSURE_H
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.cpp
new file mode 100644
index 0000000..2e3a0ec
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.cpp
@@ -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.
+ */
+
+// Native function to extract histogram from image (handed down as ByteBuffer).
+
+#include "frametovalues.h"
+
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+#include "imgprocutil.h"
+
+jboolean Java_androidx_media_filterpacks_image_ToGrayValuesFilter_toGrayValues(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject grayBuffer )
+{
+    unsigned char* pixelPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    unsigned char* grayPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(grayBuffer));
+
+    if (pixelPtr == 0 || grayPtr == 0) {
+      return JNI_FALSE;
+    }
+
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer) / 4;
+
+    // TODO: the current implementation is focused on the correctness not performance.
+    // If performance becomes an issue, it is better to increment pixelPtr directly.
+    int disp = 0;
+    for(int idx = 0; idx < numPixels; idx++, disp+=4) {
+      int R = *(pixelPtr + disp);
+      int G = *(pixelPtr + disp + 1);
+      int B = *(pixelPtr + disp + 2);
+      int gray = getIntensityFast(R, G, B);
+      *(grayPtr+idx) = static_cast<unsigned char>(gray);
+    }
+
+    return JNI_TRUE;
+}
+
+jboolean Java_androidx_media_filterpacks_image_ToRgbValuesFilter_toRgbValues(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject rgbBuffer )
+{
+    unsigned char* pixelPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    unsigned char* rgbPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(rgbBuffer));
+
+    if (pixelPtr == 0 || rgbPtr == 0) {
+      return JNI_FALSE;
+    }
+
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer) / 4;
+
+    // TODO: this code could be revised to improve the performance as the TODO above.
+    int pixelDisp = 0;
+    int rgbDisp = 0;
+    for(int idx = 0; idx < numPixels; idx++, pixelDisp += 4, rgbDisp += 3) {
+      for (int c = 0; c < 3; ++c) {
+        *(rgbPtr + rgbDisp + c) = *(pixelPtr + pixelDisp + c);
+      }
+    }
+    return JNI_TRUE;
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.h
new file mode 100644
index 0000000..4abb848
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/frametovalues.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+// Native functions to pack a RGBA frame into either a one channel grayscale buffer
+// or a three channel RGB buffer.
+
+#ifndef ANDROID_FILTERFW_JNI_TOGRAYVALUES_H
+#define ANDROID_FILTERFW_JNI_TOGRAYVALUES_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_androidx_media_filterpacks_image_ToGrayValuesFilter_toGrayValues(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject grayBuffer );
+
+JNIEXPORT jboolean JNICALL
+Java_androidx_media_filterpacks_image_ToRgbValuesFilter_toRgbValues(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject rgbBuffer );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_TOGRAYVALUES_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.cpp
new file mode 100644
index 0000000..ba060d4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+// Native function to extract histogram from image (handed down as ByteBuffer).
+
+#include "histogram.h"
+
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+#include "imgprocutil.h"
+
+inline void addPixelToHistogram(unsigned char*& pImg, int* pHist, int numBins) {
+    int R = *(pImg++);
+    int G = *(pImg++);
+    int B = *(pImg++);
+    ++pImg;
+    int i = getIntensityFast(R, G, B);
+    int bin = clamp(0, static_cast<int>(static_cast<float>(i * numBins) / 255.0f), numBins - 1);
+    ++pHist[bin];
+}
+
+void Java_androidx_media_filterpacks_histogram_GrayHistogramFilter_extractHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject maskBuffer, jobject histogramBuffer )
+{
+    unsigned char* pImg = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    int* pHist = static_cast<int*>(env->GetDirectBufferAddress(histogramBuffer));
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer) / 4;  // 4 bytes per pixel
+    int numBins    = env->GetDirectBufferCapacity(histogramBuffer);
+
+    unsigned char* pMask = NULL;
+    if(maskBuffer != NULL) {
+        pMask = static_cast<unsigned char*>(env->GetDirectBufferAddress(maskBuffer));
+    }
+
+    for(int i = 0; i < numBins; ++i) pHist[i] = 0;
+
+    if(pMask == NULL) {
+        for( ; numPixels > 0; --numPixels) {
+            addPixelToHistogram(pImg, pHist, numBins);
+        }
+    } else {
+        for( ; numPixels > 0; --numPixels) {
+            if(*pMask == 0){
+                pMask += 4;
+                pImg  += 4;  // Note that otherwise addPixelToHistogram advances pImg by 4
+                continue;
+            }
+            pMask += 4;
+            addPixelToHistogram(pImg, pHist, numBins);
+        }
+    }
+}
+
+void Java_androidx_media_filterpacks_histogram_ChromaHistogramFilter_extractChromaHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject histogramBuffer, jint hBins, jint sBins)
+{
+    unsigned char* pixelIn = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    float* histOut = static_cast<float*>(env->GetDirectBufferAddress(histogramBuffer));
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer) / 4;  // 4 bytes per pixel
+
+    for (int i = 0; i < hBins * sBins; ++i) histOut[i] = 0.0f;
+
+    int h, s, v;
+    float hScaler = hBins / 256.0f;
+    float sScaler = sBins / 256.0f;
+    for( ; numPixels > 0; --numPixels) {
+      h = *(pixelIn++);
+      s = *(pixelIn++);
+      v = *(pixelIn++);
+      pixelIn++;
+
+      int index = static_cast<int>(s * sScaler) * hBins + static_cast<int>(h * hScaler);
+      histOut[index] += 1.0f;
+    }
+}
+
+void Java_androidx_media_filterpacks_histogram_NewChromaHistogramFilter_extractChromaHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject histogramBuffer,
+    jint hueBins, jint saturationBins, jint valueBins,
+    jint saturationThreshold, jint valueThreshold) {
+    unsigned char* pixelIn = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    float* histOut = static_cast<float*>(env->GetDirectBufferAddress(histogramBuffer));
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer) / 4;  // 4 bytes per pixel
+
+    // TODO: add check on the size of histOut
+    for (int i = 0; i < (hueBins * saturationBins + valueBins); ++i) {
+      histOut[i] = 0.0f;
+    }
+
+    for( ; numPixels > 0; --numPixels) {
+      int h = *(pixelIn++);
+      int s = *(pixelIn++);
+      int v = *(pixelIn++);
+
+      pixelIn++;
+      // If a pixel that is either too dark (less than valueThreshold) or colorless
+      // (less than saturationThreshold), if will be put in a 1-D value histogram instead.
+
+      int index;
+      if (s > saturationThreshold && v > valueThreshold) {
+        int sIndex = s * saturationBins / 256;
+
+        // Shifting hue index by 0.5 such that peaks of red, yellow, green, cyan, blue, pink
+        // will be at the center of some bins.
+        int hIndex = ((h * hueBins + 128) / 256) % hueBins;
+        index = sIndex * hueBins + hIndex;
+      } else {
+        index =  hueBins * saturationBins + (v * valueBins / 256);
+      }
+      histOut[index] += 1.0f;
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.h
new file mode 100644
index 0000000..b5e88aa
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/histogram.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+// Native function to extract histogram from image (handed down as ByteBuffer).
+
+#ifndef ANDROID_FILTERFW_JNI_HISTOGRAM_H
+#define ANDROID_FILTERFW_JNI_HISTOGRAM_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL
+Java_androidx_media_filterpacks_histogram_GrayHistogramFilter_extractHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject maskBuffer, jobject histogramBuffer );
+
+JNIEXPORT void JNICALL
+Java_androidx_media_filterpacks_histogram_ChromaHistogramFilter_extractChromaHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject histogramBuffer, jint hBins, jint sBins);
+
+JNIEXPORT void JNICALL
+Java_androidx_media_filterpacks_histogram_NewChromaHistogramFilter_extractChromaHistogram(
+    JNIEnv* env, jclass clazz, jobject imageBuffer, jobject histogramBuffer,
+    jint hueBins, jint saturationBins, jint valueBins,
+    jint saturationThreshold, jint valueThreshold);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_HISTOGRAM_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/imgprocutil.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/imgprocutil.h
new file mode 100644
index 0000000..aef67a5
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/imgprocutil.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+// Some native low-level image processing functions.
+
+
+#ifndef ANDROID_FILTERFW_JNI_IMGPROCUTIL_H
+#define ANDROID_FILTERFW_JNI_IMGPROCUTIL_H
+
+inline int getIntensityFast(int R, int G, int B) {
+    return (R + R + R + B + G + G + G + G) >> 3;  // see http://stackoverflow.com/a/596241
+}
+
+inline int clamp(int min, int val, int max) {
+    return val < min ? min : (val > max ? max : val);
+        // Note that for performance reasons, this function does *not* check if min < max!
+}
+
+#endif // ANDROID_FILTERFW_JNI_IMGPROCUTIL_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.cpp
new file mode 100644
index 0000000..596c7c0
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "pixelutils.h"
+
+#include <stdint.h>
+
+typedef uint32_t uint32;
+
+void JNI_PIXELUTILS_METHOD(nativeCopyPixels)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height, jint offset,
+    jint pixStride, jint rowStride) {
+  uint32* pInPix = static_cast<uint32*>(env->GetDirectBufferAddress(input));
+  uint32* pOutput = static_cast<uint32*>(env->GetDirectBufferAddress(output));
+  uint32* pOutRow = pOutput + offset;
+  for (int y = 0; y < height; ++y) {
+    uint32* pOutPix = pOutRow;
+    for (int x = 0; x < width; ++x) {
+      *pOutPix = *(pInPix++);
+      pOutPix += pixStride;
+    }
+    pOutRow += rowStride;
+  }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.h
new file mode 100644
index 0000000..be69009
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/pixelutils.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_PIXELUTILS_H
+#define ANDROID_FILTERFW_JNI_PIXELUTILS_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define JNI_PIXELUTILS_METHOD(METHOD_NAME) \
+  Java_androidx_media_filterfw_PixelUtils_ ## METHOD_NAME
+
+JNIEXPORT void JNICALL
+JNI_PIXELUTILS_METHOD(nativeCopyPixels)(
+    JNIEnv* env, jclass clazz, jobject input, jobject output, jint width, jint height, jint offset,
+    jint pixStride, jint rowStride);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_PIXELUTILS_H
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.cpp
new file mode 100644
index 0000000..dc5c305
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+// Native function to extract histogram from image (handed down as ByteBuffer).
+
+#include "sobeloperator.h"
+
+#include <math.h>
+#include <string.h>
+#include <jni.h>
+#include <unistd.h>
+#include <android/log.h>
+
+#include "imgprocutil.h"
+
+/*
+ * Perform 1d convolution on 3 channel image either horizontally or vertically.
+ * Parameters:
+ *  inputHead: pointer to input image
+ *  length: the length of image in the chosen axis.
+ *  fragments: number of lines of the image in the chosen axis.
+ *  step: the 1d pixel distance between adjacent pixels in the chosen axis.
+ *  shift: the 1d pixel distance between adjacent lines in the chosen axis.
+ *  filter: pointer to 1d filter
+ *  halfSize: the length of filter is supposed to be (2 * halfSize + 1)
+ *  outputHead: pointer to output image
+ */
+
+void computeGradient(unsigned char* dataPtr, int width, int height, short* gxPtr, short* gyPtr) {
+  for (int i = 0; i < height; i++) {
+    for (int j = 0; j < width; j++) {
+      const int left = (j > 0)? -4 : 0;
+      const int right = (j < width - 1) ? 4 : 0;
+      const int curr = (i * width + j) * 4;
+      const int above = (i > 0) ? curr - 4 * width : curr;
+      const int below = (i < height - 1) ? curr + 4 * width : curr;
+      const int offset = (i * width + j) * 3;
+      for (int c = 0; c < 3; c++) {
+        *(gxPtr + offset + c) =
+            (*(dataPtr + curr + c + right) - *(dataPtr + curr + c + left)) * 2 +
+            *(dataPtr + above + c + right) - *(dataPtr + above + c + left) +
+            *(dataPtr + below + c + right) - *(dataPtr + below + c + left);
+        *(gyPtr + offset + c) =
+            (*(dataPtr + c + below) - *(dataPtr + c + above)) * 2 +
+            *(dataPtr + left + c + below) - *(dataPtr + left + c + above) +
+            *(dataPtr + right + c + below) - *(dataPtr + right + c + above);
+      }
+    }
+  }
+}
+
+jboolean Java_androidx_media_filterpacks_image_SobelFilter_sobelOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height, jobject imageBuffer,
+    jobject magBuffer, jobject dirBuffer) {
+
+  if (imageBuffer == 0) {
+    return JNI_FALSE;
+  }
+  unsigned char* srcPtr = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+  unsigned char* magPtr = (magBuffer == 0) ?
+      0 : static_cast<unsigned char*>(env->GetDirectBufferAddress(magBuffer));
+  unsigned char* dirPtr = (dirBuffer == 0) ?
+      0 : static_cast<unsigned char*>(env->GetDirectBufferAddress(dirBuffer));
+
+  int numPixels = width * height;
+  // TODO: avoid creating and deleting these buffers within this native function.
+  short* gxPtr = new short[3 * numPixels];
+  short* gyPtr = new short[3 * numPixels];
+  computeGradient(srcPtr, width, height, gxPtr, gyPtr);
+
+  unsigned char* mag = magPtr;
+  unsigned char* dir = dirPtr;
+  for (int i = 0; i < numPixels; ++i) {
+    for (int c = 0; c < 3; c++) {
+      int gx = static_cast<int>(*(gxPtr + 3 * i + c) / 8 + 127.5);
+      int gy = static_cast<int>(*(gyPtr + 3 * i + c) / 8 + 127.5);
+
+      // emulate arithmetic in GPU.
+      gx = 2 * gx - 255;
+      gy = 2 * gy - 255;
+      if (magPtr != 0) {
+        double value = sqrt(gx * gx + gy * gy);
+        *(magPtr + 4 * i + c) = static_cast<unsigned char>(value);
+      }
+      if (dirPtr != 0) {
+        *(dirPtr + 4 * i + c) = static_cast<unsigned char>(
+            (atan(static_cast<double>(gy)/static_cast<double>(gx)) + 3.14) / 6.28);
+      }
+    }
+    //setting alpha change to 1.0 (255)
+    if (magPtr != 0) {
+      *(magPtr + 4 * i + 3) = 255;
+    }
+    if (dirPtr != 0) {
+      *(dirPtr + 4 * i + 3) = 255;
+    }
+  }
+
+  delete[] gxPtr;
+  delete[] gyPtr;
+
+  return JNI_TRUE;
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.h
new file mode 100644
index 0000000..c7639d2
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/sobeloperator.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// Native function to extract histogram from image (handed down as ByteBuffer).
+
+#ifndef ANDROID_FILTERFW_JNI_SOBELOPERATOR_H
+#define ANDROID_FILTERFW_JNI_SOBELOPERATOR_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_androidx_media_filterpacks_image_SobelFilter_sobelOperator(
+    JNIEnv* env, jclass clazz, jint width, jint height,
+    jobject imageBuffer, jobject magBuffer, jobject dirBuffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_SOBELOPERATOR_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.cpp b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.cpp
new file mode 100644
index 0000000..f282675
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+// Stats (mean and stdev) scoring in the native.
+
+#include "stats_scorer.h"
+
+#include <jni.h>
+#include <math.h>
+
+void Java_androidx_media_filterpacks_numeric_StatsFilter_score(
+    JNIEnv* env, jobject thiz, jobject imageBuffer, jfloatArray statsArray)
+{
+    unsigned char* pImg = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    int numPixels  = env->GetDirectBufferCapacity(imageBuffer);  // 1 byte per pixel
+    float sum = 0.0;
+    float sumSquares = 0.0;
+
+    for (int i = 0; i < numPixels; ++i) {
+        float val = static_cast<float>(pImg[i]);
+        sum += val;
+        sumSquares += val * val;
+    }
+    jfloat result[2];
+    result[0] = sum / numPixels;  // mean
+    result[1] = sqrt((sumSquares - numPixels * result[0] * result[0]) / (numPixels - 1));  // stdev.
+    env->SetFloatArrayRegion(statsArray, 0, 2, result);
+}
+
+void Java_androidx_media_filterpacks_numeric_StatsFilter_regionscore(
+    JNIEnv* env, jobject thiz, jobject imageBuffer, jint width, jint height,
+    jfloat left, jfloat top, jfloat right, jfloat bottom, jfloatArray statsArray)
+{
+    unsigned char* pImg = static_cast<unsigned char*>(env->GetDirectBufferAddress(imageBuffer));
+    int xStart = static_cast<int>(width * left);
+    int xEnd = static_cast<int>(width * right);
+    int yStart = static_cast<int>(height * top);
+    int yEnd = static_cast<int>(height * bottom);
+    int numPixels  = (xEnd - xStart) * (yEnd - yStart);
+    float sum = 0.0;
+    float sumSquares = 0.0;
+
+    for (int y = yStart; y < yEnd; y++) {
+      int disp = width * y;
+      for (int x = xStart; x < xEnd; ++x) {
+        float val = static_cast<float>(*(pImg + disp + x));
+        sum += val;
+        sumSquares += val * val;
+      }
+    }
+    jfloat result[2];
+    result[0] = sum / numPixels;  // mean
+    result[1] = (numPixels == 1) ?
+        0 : sqrt((sumSquares - numPixels * result[0] * result[0]) / (numPixels - 1));  // stdev.
+    env->SetFloatArrayRegion(statsArray, 0, 2, result);
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.h b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.h
new file mode 100644
index 0000000..a951ec9
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/jni/stats_scorer.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+// Stats (mean and stdev) scoring in the native.
+
+#ifndef ANDROID_FILTERFW_JNI_STATS_SCORER_H
+#define ANDROID_FILTERFW_JNI_STATS_SCORER_H
+
+#include <jni.h>
+
+#define JNI_FES_FUNCTION(name) Java_androidx_media_filterpacks_numeric_StatsFilter_ ## name
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL
+JNI_FES_FUNCTION(score)(
+    JNIEnv* env, jobject thiz, jobject imageBuffer, jfloatArray statsArray);
+
+JNIEXPORT void JNICALL
+JNI_FES_FUNCTION(regionscore)(
+   JNIEnv* env, jobject thiz, jobject imageBuffer, jint width, jint height,
+   jfloat lefp, jfloat top, jfloat right, jfloat bottom, jfloatArray statsArray);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif  // ANDROID_FILTERFW_JNI_STATS_SCORER_H
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/proguard-project.txt b/tests/Camera2Tests/SmartCamera/SimpleCamera/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/project.properties b/tests/Camera2Tests/SmartCamera/SimpleCamera/project.properties
new file mode 100644
index 0000000..10149cb
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-16
+android.library.reference.1=../../filterfw
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/black_screen.jpg b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/black_screen.jpg
new file mode 100644
index 0000000..702d9fa
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/black_screen.jpg
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_launcher.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..1c7b44a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_gallery.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_gallery.png
new file mode 100644
index 0000000..f61bbd8
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_gallery.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_quill.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_quill.png
new file mode 100644
index 0000000..7ea01b7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_quill.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_save.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_save.png
new file mode 100644
index 0000000..62d0b9a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-hdpi/ic_menu_save.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-ldpi/ic_launcher.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..b42e903
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-mdpi/ic_launcher.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..d4b4d6b
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/android_figure.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/android_figure.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/android_figure.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/oldframe.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/oldframe.png
new file mode 100644
index 0000000..8b7ae63
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/oldframe.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/polaroid.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/polaroid.png
new file mode 100644
index 0000000..5504c57
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-nodpi/polaroid.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-xhdpi/ic_launcher.png b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..3bb5454
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/imageview.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/imageview.xml
new file mode 100644
index 0000000..4e20c3f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/imageview.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="180px"
+    android:layout_height="240px"
+    android:src="@drawable/black_screen"
+    android:adjustViewBounds="true"
+/>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/simplecamera.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/simplecamera.xml
new file mode 100644
index 0000000..8d8ff51
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/layout/simplecamera.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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">
+
+    <RelativeLayout android:id="@+id/surfaceViewLayout"
+        android:layout_width="wrap_content"
+        android:layout_height="1240px"
+        android:layout_alignParentTop="true" >
+        <SurfaceView android:id="@+id/cameraView"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1.0"
+        />
+        <Button android:id="@+id/startButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/startButton"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        />
+        <Button android:id="@+id/galleryOpenButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/galleryOpenButton"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        />
+        <Spinner android:id="@+id/spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:entries="@array/number_array"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        />
+        <TextView android:id="@+id/imagesSavedTextView"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:padding="16dip"
+        android:text="@string/imagesSavedTextView"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentBottom="true"
+        android:textColor="#FF0000"
+        android:textSize="20sp"
+        />
+    </RelativeLayout>
+    <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/scrollView"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/scrollViewLinearLayout"
+      android:orientation="horizontal"
+      android:layout_width="fill_parent"
+      android:layout_height="320px">
+    </LinearLayout>
+    </HorizontalScrollView>
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:orientation="horizontal"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content">
+        <TextView android:id="@+id/goodOrBadTextView"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:padding="16dip"
+        android:text="@string/goodOrBadTextView"
+        />
+        <TextView android:id="@+id/fpsTextView"
+        android:layout_height="fill_parent"
+        android:layout_width="wrap_content"
+        android:padding="16dip"
+        android:text="@string/fpsTextView"
+        />
+        <TextView android:id="@+id/scoreTextView"
+        android:layout_height="fill_parent"
+        android:layout_width="wrap_content"
+        android:padding="16dip"
+        android:text="@string/scoreTextView"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/raw/camera_graph.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/raw/camera_graph.xml
new file mode 100644
index 0000000..6661fd7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/raw/camera_graph.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<graph>
+    <!-- Packages -->
+    <import package="androidx.media.filterpacks.base"/>
+    <import package="androidx.media.filterpacks.image"/>
+    <import package="androidx.media.filterpacks.video"/>
+    <import package="androidx.media.filterpacks.text" />
+    <import package="androidx.media.filterpacks.numeric" />
+    <import package="androidx.media.filterpacks.face" />
+    <import package="androidx.media.filterpacks.transform" />
+    <import package="androidx.media.filterpacks.performance" />
+    <import package="androidx.media.filterfw.samples.simplecamera" />
+    <import package="androidx.media.filterpacks.histogram" />
+    <import package="androidx.media.filterpacks.colorspace" />
+    <import package="androidx.media.filterpacks.sensors" />
+
+    <!-- Filters -->
+    <filter class="ResizeFilter" name="resize" >
+        <input name="outputWidth" intValue="480" />
+        <input name="outputHeight" intValue="640" />
+    </filter>
+
+    <filter class="Camera2Source" name="camera"/>
+
+    <filter class="BranchFilter" name="mainBranch" />
+    <filter class="BranchFilter" name="preMainBranch" />
+    <filter class="BranchFilter" name="featureBranch" />
+
+    <filter class="SurfaceHolderTarget" name="camViewTarget"/>
+
+    <filter class="ScaleFilter" name="scale" >
+        <input name="scale" floatValue="0.50"/>
+    </filter>
+
+    <filter class="SobelFilter" name="sobel" />
+    <filter class="StatsFilter" name="statsFilter" />
+    <filter class="NormFilter" name="normFilter" />
+    <filter class="TextViewTarget" name="goodOrBadTextView" />
+    <filter class="ToGrayValuesFilter" name="sobelConverter" />
+    <filter class="AverageFilter" name="avgFilter" />
+
+    <var name="startCapture" />
+    <filter class="ImageGoodnessFilter" name="goodnessFilter" >
+        <input name="capturing" varValue="startCapture" />
+    </filter>
+
+    <filter class="ToStringFilter" name="scoreToString" />
+    <filter class="TextViewTarget" name="scoreTextView" />
+
+    <filter class="ExposureFilter" name="exposure" />
+
+    <filter class="TextViewTarget" name="fpsTextView" />
+    <filter class="ToStringFilter" name="throughputToString" />
+
+
+    <filter class="ContrastRatioFilter" name="contrast" />
+
+    <filter class="ScaleFilter" name="secondaryScale" >
+        <input name="scale" floatValue="0.50"/>
+    </filter>
+
+    <filter class="ThroughputFilter" name="throughput" />
+
+    <filter class="NewChromaHistogramFilter" name="histogram" />
+    <filter class="ColorfulnessFilter" name="colorfulness" />
+
+    <filter class="MotionSensorWTime" name="motion" />
+
+    <filter class="AvgBrightnessFilter" name="brightness" />
+
+    <filter class="RotateFilter" name="rotate" />
+
+    <filter class="BrightnessFilter" name="snapBrightness" />
+    <filter class="WaveTriggerFilter" name="snapEffect" />
+    <!-- Connections -->
+    <connect sourceFilter="camera" sourcePort="video"
+        targetFilter="rotate" targetPort="image" />
+
+    <connect sourceFilter="camera" sourcePort="orientation"
+        targetFilter="rotate" targetPort="rotateAngle" />
+
+    <connect sourceFilter="rotate" sourcePort="image"
+        targetFilter="resize" targetPort="image" />
+    <connect sourceFilter="resize" sourcePort="image"
+        targetFilter="preMainBranch" targetPort="input" />
+    <connect sourceFilter="preMainBranch" sourcePort="toMainBranch"
+        targetFilter="scale" targetPort="image" />
+    <connect sourceFilter="scale" sourcePort="image"
+         targetFilter="mainBranch" targetPort="input" />
+
+    <connect sourceFilter="preMainBranch" sourcePort="toGoodnessFilter"
+        targetFilter="goodnessFilter" targetPort="image" />
+    <connect sourceFilter="mainBranch" sourcePort="toFeatureBranch"
+        targetFilter="secondaryScale" targetPort="image" />
+    <connect sourceFilter="secondaryScale" sourcePort="image"
+        targetFilter="featureBranch" targetPort="input" />
+
+    <connect sourceFilter="featureBranch" sourcePort="toSobel"
+         targetFilter="sobel" targetPort="image" />
+
+    <connect sourceFilter="sobel" sourcePort="magnitude"
+         targetFilter="sobelConverter" targetPort="image" />
+
+    <connect sourceFilter="sobelConverter" sourcePort="image"
+         targetFilter="statsFilter" targetPort="buffer" />
+
+    <connect sourceFilter="statsFilter" sourcePort="mean"
+         targetFilter="normFilter" targetPort="x" />
+
+    <connect sourceFilter="statsFilter" sourcePort="stdev"
+         targetFilter="normFilter" targetPort="y" />
+
+    <connect sourceFilter="normFilter" sourcePort="norm"
+         targetFilter="avgFilter" targetPort="sharpness" />
+
+    <connect sourceFilter="avgFilter" sourcePort="avg"
+         targetFilter="goodnessFilter" targetPort="sharpness" />
+
+    <connect sourceFilter="goodnessFilter" sourcePort="goodOrBadPic"
+         targetFilter="goodOrBadTextView" targetPort="text" />
+
+    <connect sourceFilter="featureBranch" sourcePort="toExposure"
+        targetFilter="exposure" targetPort="image" />
+    <connect sourceFilter="exposure" sourcePort="underExposureRating"
+        targetFilter="goodnessFilter" targetPort="underExposure" />
+    <connect sourceFilter="exposure" sourcePort="overExposureRating"
+        targetFilter="goodnessFilter" targetPort="overExposure" />
+
+    <connect sourceFilter="goodnessFilter" sourcePort="score"
+        targetFilter="scoreToString" targetPort="object" />
+    <connect sourceFilter="scoreToString" sourcePort="string"
+        targetFilter="scoreTextView" targetPort="text" />
+
+    <connect sourceFilter="mainBranch" sourcePort="camView"
+        targetFilter="throughput" targetPort="frame" />
+    <connect sourceFilter="throughput" sourcePort="frame"
+        targetFilter="snapBrightness" targetPort="image" />
+    <connect sourceFilter="snapEffect" sourcePort="value"
+        targetFilter="snapBrightness" targetPort="brightness" />
+    <connect sourceFilter="snapBrightness" sourcePort="image"
+        targetFilter="camViewTarget" targetPort="image" />
+    <connect sourceFilter="throughput" sourcePort="throughput"
+        targetFilter="throughputToString" targetPort="object" />
+    <connect sourceFilter="throughputToString" sourcePort="string"
+        targetFilter="fpsTextView" targetPort="text" />
+
+    <connect sourceFilter="featureBranch" sourcePort="contrastRatio"
+        targetFilter="contrast" targetPort="image" />
+    <connect sourceFilter="contrast" sourcePort="contrastRatingToGoodness"
+        targetFilter="goodnessFilter" targetPort="contrastRating" />
+
+    <connect sourceFilter="mainBranch" sourcePort="colorfulness"
+        targetFilter="histogram" targetPort="image" />
+    <connect sourceFilter="histogram" sourcePort="histogram"
+        targetFilter="colorfulness" targetPort="histogram" />
+    <connect sourceFilter="colorfulness" sourcePort="score"
+        targetFilter="goodnessFilter" targetPort="colorfulness" />
+
+    <connect sourceFilter="motion" sourcePort="values"
+        targetFilter="goodnessFilter" targetPort="motionValues" />
+
+    <connect sourceFilter="featureBranch" sourcePort="brightness"
+        targetFilter="brightness" targetPort="image" />
+    <connect sourceFilter="brightness" sourcePort="brightnessRating"
+        targetFilter="goodnessFilter" targetPort="brightness" />
+</graph>
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v11/styles.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v11/styles.xml
new file mode 100644
index 0000000..d408cbc
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v11/styles.xml
@@ -0,0 +1,5 @@
+<resources>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v14/styles.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v14/styles.xml
new file mode 100644
index 0000000..1c089a7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values-v14/styles.xml
@@ -0,0 +1,5 @@
+<resources>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/strings.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/strings.xml
new file mode 100644
index 0000000..5e6b8ab
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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="goodOrBadTextView"> Good/Bad Picture </string>
+     <string name="fpsTextView"> FPS </string>
+     <string name="scoreTextView"> Score</string>
+     <string name="gallery"> Go To Gallery </string>
+     <string name="camera"> Go To Camera </string>
+     <string name="startButton" > Start </string>
+     <string name="imagesSavedTextView" > Images Saved </string>
+     <string name="galleryOpenButton" > Gallery </string>
+     <string-array name="number_array">
+        <item> 1 </item>
+        <item> 2 </item>
+        <item> 3 </item>
+        <item> 4 </item>
+        <item> 5 </item>
+        <item> 6 </item>
+        <item> 7 </item>
+        <item> 8 </item>
+        <item> 9 </item>
+        <item> 10 </item>
+     </string-array>
+</resources>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/styles.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/styles.xml
new file mode 100644
index 0000000..bd5027f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/res/values/styles.xml
@@ -0,0 +1,5 @@
+<resources>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BackingStore.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BackingStore.java
new file mode 100644
index 0000000..216e743
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BackingStore.java
@@ -0,0 +1,929 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Vector;
+
+final class BackingStore {
+
+    /** Access mode None: Frame data will not be accessed at all. */
+    static final int ACCESS_NONE = 0x00;
+    /** Access mode Bytes: Frame data will be accessed as a ByteBuffer. */
+    static final int ACCESS_BYTES = 0x01;
+    /** Access mode Texture: Frame data will be accessed as a TextureSource. */
+    static final int ACCESS_TEXTURE = 0x02;
+    /** Access mode RenderTarget: Frame data will be accessed as a RenderTarget. */
+    static final int ACCESS_RENDERTARGET = 0x04;
+    /** Access mode Object: Frame data will be accessed as a generic Object. */
+    static final int ACCESS_OBJECT = 0x08;
+    /** Access mode Bitmap: Frame data will be accessed as a Bitmap. */
+    static final int ACCESS_BITMAP = 0x10;
+    /** Access mode Allocation: Frame data will be accessed as a RenderScript Allocation. */
+    static final int ACCESS_ALLOCATION = 0x20;
+
+    private static final int BACKING_BYTEBUFFER = 1;
+    private static final int BACKING_TEXTURE = 2;
+    private static final int BACKING_OBJECT = 3;
+    private static final int BACKING_BITMAP = 4;
+    private static final int BACKING_ALLOCATION = 5;
+
+    private final FrameType mType;
+    private int[] mDimensions;
+    private long mTimestamp = Frame.TIMESTAMP_NOT_SET;
+
+    private final FrameManager mFrameManager;
+
+    private Vector<Backing> mBackings = new Vector<Backing>();
+
+    private boolean mWriteLocked = false;
+    private int mReadLocks = 0;
+
+    private int mRefCount = 1;
+
+    /** The most up-to-date data backing */
+    private Backing mCurrentBacking = null;
+
+    /** The currently locked backing */
+    private Backing mLockedBacking = null;
+
+    // Public Methods //////////////////////////////////////////////////////////////////////////////
+    public BackingStore(FrameType type, int[] dimensions, FrameManager frameManager) {
+        mType = type;
+        mDimensions = dimensions != null ? Arrays.copyOf(dimensions, dimensions.length) : null;
+        mFrameManager = frameManager;
+    }
+
+    public FrameType getFrameType() {
+        return mType;
+    }
+
+    public Object lockData(int mode, int accessFormat) {
+        return lockBacking(mode, accessFormat).lock(accessFormat);
+    }
+
+    public Backing lockBacking(int mode, int access) {
+        Backing backing = fetchBacking(mode, access);
+        if (backing == null) {
+            throw new RuntimeException("Could not fetch frame data!");
+        }
+        lock(backing, mode);
+        return backing;
+    }
+
+    public boolean unlock() {
+        if (mWriteLocked) {
+            mWriteLocked = false;
+        } else if (mReadLocks > 0) {
+            --mReadLocks;
+        } else {
+            return false;
+        }
+        mLockedBacking.unlock();
+        mLockedBacking = null;
+        return true;
+    }
+
+    public BackingStore retain() {
+        if (mRefCount >= 10) {
+            Log.w("BackingStore", "High ref-count of " + mRefCount + " on " + this + "!");
+        }
+        if (mRefCount <= 0) {
+            throw new RuntimeException("RETAINING RELEASED");
+        }
+        ++mRefCount;
+        return this;
+    }
+
+    public BackingStore release() {
+        if (mRefCount <= 0) {
+            throw new RuntimeException("DOUBLE-RELEASE");
+        }
+        --mRefCount;
+        if (mRefCount == 0) {
+            releaseBackings();
+            return null;
+        }
+        return this;
+    }
+
+    /**
+     * Resizes the backing store. This invalidates all data in the store.
+     */
+    public void resize(int[] newDimensions) {
+        Vector<Backing> resized = new Vector<Backing>();
+        for (Backing backing : mBackings) {
+            if (backing.resize(newDimensions)) {
+                resized.add(backing);
+            } else {
+                releaseBacking(backing);
+            }
+        }
+        mBackings = resized;
+        mDimensions = newDimensions;
+    }
+
+    public int[] getDimensions() {
+        return mDimensions;
+    }
+
+    public int getElementCount() {
+        int result = 1;
+        if (mDimensions != null) {
+            for (int dim : mDimensions) {
+                result *= dim;
+            }
+        }
+        return result;
+    }
+
+    public void importStore(BackingStore store) {
+        // TODO: Better backing selection?
+        if (store.mBackings.size() > 0) {
+            importBacking(store.mBackings.firstElement());
+        }
+        mTimestamp = store.mTimestamp;
+    }
+
+    /**
+     * @return the timestamp
+     */
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * @param timestamp the timestamp to set
+     */
+    public void setTimestamp(long timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    // Internal Methods ////////////////////////////////////////////////////////////////////////////
+    private Backing fetchBacking(int mode, int access) {
+        Backing backing = getBacking(mode, access);
+        if (backing == null) {
+            backing = attachNewBacking(mode, access);
+        }
+        syncBacking(backing);
+        return backing;
+    }
+
+    private void syncBacking(Backing backing) {
+        if (backing != null && backing.isDirty() && mCurrentBacking != null) {
+            backing.syncTo(mCurrentBacking);
+        }
+    }
+
+    private Backing getBacking(int mode, int access) {
+        // [Non-iterator looping]
+        for (int i = 0; i < mBackings.size(); ++i) {
+            final Backing backing = mBackings.get(i);
+
+            int backingAccess =
+                    (mode == Frame.MODE_WRITE) ? backing.writeAccess() : backing.readAccess();
+            if ((backingAccess & access) == access) {
+                return backing;
+            }
+        }
+        return null;
+    }
+
+    private Backing attachNewBacking(int mode, int access) {
+        Backing backing = createBacking(mode, access);
+        if (mBackings.size() > 0) {
+            backing.markDirty();
+        }
+        mBackings.add(backing);
+        return backing;
+    }
+
+    private Backing createBacking(int mode, int access) {
+        // TODO: If the read/write access flags indicate, make/fetch a GraphicBuffer backing.
+        Backing backing = null;
+        int elemSize = mType.getElementSize();
+        if (shouldFetchCached(access)) {
+            backing = mFrameManager.fetchBacking(mode, access, mDimensions, elemSize);
+        }
+        if (backing == null) {
+            switch (access) {
+                case ACCESS_BYTES:
+                    backing = new ByteBufferBacking();
+                    break;
+                case ACCESS_TEXTURE:
+                case ACCESS_RENDERTARGET:
+                    backing = new TextureBacking();
+                    break;
+                case ACCESS_OBJECT:
+                    backing = new ObjectBacking();
+                    break;
+                case ACCESS_BITMAP:
+                    backing = new BitmapBacking();
+                    break;
+                case ACCESS_ALLOCATION:
+                    if (!AllocationBacking.isSupported()) {
+                        throw new RuntimeException(
+                                "Attempted to create an AllocationBacking in context that does " +
+                                "not support RenderScript!");
+                    }
+                    backing = new AllocationBacking(mFrameManager.getContext().getRenderScript());
+                    break;
+            }
+            if (backing == null) {
+                throw new RuntimeException(
+                        "Could not create backing for access type " + access + "!");
+            }
+            if (backing.requiresGpu() && !mFrameManager.getRunner().isOpenGLSupported()) {
+                throw new RuntimeException(
+                        "Cannot create backing that requires GPU in a runner that does not " +
+                        "support OpenGL!");
+            }
+            backing.setDimensions(mDimensions);
+            backing.setElementSize(elemSize);
+            backing.setElementId(mType.getElementId());
+            backing.allocate(mType);
+            mFrameManager.onBackingCreated(backing);
+        }
+        return backing;
+    }
+
+    private void importBacking(Backing backing) {
+        // TODO: This actually needs synchronization between the two BackingStore threads for the
+        // general case
+        int access = backing.requiresGpu() ? ACCESS_BYTES : backing.readAccess();
+        Backing newBacking = createBacking(Frame.MODE_READ, access);
+        newBacking.syncTo(backing);
+        mBackings.add(newBacking);
+        mCurrentBacking = newBacking;
+    }
+
+    private void releaseBackings() {
+        // [Non-iterator looping]
+        for (int i = 0; i < mBackings.size(); ++i) {
+            releaseBacking(mBackings.get(i));
+        }
+        mBackings.clear();
+        mCurrentBacking = null;
+    }
+
+    private void releaseBacking(Backing backing) {
+        mFrameManager.onBackingAvailable(backing);
+    }
+
+    private void lock(Backing backingToLock, int mode) {
+        if (mode == Frame.MODE_WRITE) {
+            // Make sure frame is not read-locked
+            if (mReadLocks > 0) {
+                throw new RuntimeException(
+                        "Attempting to write-lock the read-locked frame " + this + "!");
+            } else if (mWriteLocked) {
+                throw new RuntimeException(
+                        "Attempting to write-lock the write-locked frame " + this + "!");
+            }
+            // Mark all other backings dirty
+            // [Non-iterator looping]
+            for (int i = 0; i < mBackings.size(); ++i) {
+                final Backing backing = mBackings.get(i);
+                if (backing != backingToLock) {
+                    backing.markDirty();
+                }
+            }
+            mWriteLocked = true;
+            mCurrentBacking = backingToLock;
+        } else {
+            if (mWriteLocked) {
+                throw new RuntimeException("Attempting to read-lock locked frame " + this + "!");
+            }
+            ++mReadLocks;
+        }
+        mLockedBacking = backingToLock;
+    }
+
+    private static boolean shouldFetchCached(int access) {
+        return access != ACCESS_OBJECT;
+    }
+
+
+    // Backings ////////////////////////////////////////////////////////////////////////////////////
+    static abstract class Backing {
+        protected int[] mDimensions = null;
+        private int mElementSize;
+        private int mElementID;
+        protected boolean mIsDirty = false;
+
+        int cachePriority = 0;
+
+        public abstract void allocate(FrameType frameType);
+
+        public abstract int readAccess();
+
+        public abstract int writeAccess();
+
+        public abstract void syncTo(Backing backing);
+
+        public abstract Object lock(int accessType);
+
+        public abstract int getType();
+
+        public abstract boolean shouldCache();
+
+        public abstract boolean requiresGpu();
+
+        public abstract void destroy();
+
+        public abstract int getSize();
+
+        public void unlock() {
+            // Default implementation does nothing.
+        }
+
+        public void setData(Object data) {
+            throw new RuntimeException("Internal error: Setting data on frame backing " + this
+                    + ", which does not support setting data directly!");
+        }
+
+        public void setDimensions(int[] dimensions) {
+            mDimensions = dimensions;
+        }
+
+        public void setElementSize(int elemSize) {
+            mElementSize = elemSize;
+        }
+
+        public void setElementId(int elemId) {
+            mElementID = elemId;
+        }
+
+        public int[] getDimensions() {
+            return mDimensions;
+        }
+
+        public int getElementSize() {
+            return mElementSize;
+        }
+
+        public int getElementId() {
+            return mElementID;
+        }
+
+        public boolean resize(int[] newDimensions) {
+            return false;
+        }
+
+        public void markDirty() {
+            mIsDirty = true;
+        }
+
+        public boolean isDirty() {
+            return mIsDirty;
+        }
+
+        protected void assertImageCompatible(FrameType type) {
+            if (type.getElementId() != FrameType.ELEMENT_RGBA8888) {
+                throw new RuntimeException("Cannot allocate texture with non-RGBA data type!");
+            } else if (mDimensions == null || mDimensions.length != 2) {
+                throw new RuntimeException("Cannot allocate non 2-dimensional texture!");
+            }
+        }
+
+    }
+
+    static class ObjectBacking extends Backing {
+
+        private Object mObject = null;
+
+        @Override
+        public void allocate(FrameType frameType) {
+            mObject = null;
+        }
+
+        @Override
+        public int readAccess() {
+            return ACCESS_OBJECT;
+        }
+
+        @Override
+        public int writeAccess() {
+            return ACCESS_OBJECT;
+        }
+
+        @Override
+        public void syncTo(Backing backing) {
+            switch (backing.getType()) {
+                case BACKING_OBJECT:
+                    mObject = backing.lock(ACCESS_OBJECT);
+                    backing.unlock();
+                    break;
+                case BACKING_BITMAP:
+                    mObject = backing.lock(ACCESS_BITMAP);
+                    backing.unlock();
+                    break;
+                default:
+                    mObject = null;
+            }
+            mIsDirty = false;
+        }
+
+        @Override
+        public Object lock(int accessType) {
+            return mObject;
+        }
+
+        @Override
+        public int getType() {
+            return BACKING_OBJECT;
+        }
+
+        @Override
+        public boolean shouldCache() {
+            return false;
+        }
+
+        @Override
+        public boolean requiresGpu() {
+            return false;
+        }
+
+        @Override
+        public void destroy() {
+            mObject = null;
+        }
+
+        @Override
+        public int getSize() {
+            return 0;
+        }
+
+        @Override
+        public void setData(Object data) {
+            mObject = data;
+        }
+
+    }
+
+    static class BitmapBacking extends Backing {
+
+        private Bitmap mBitmap = null;
+
+        @Override
+        public void allocate(FrameType frameType) {
+            assertImageCompatible(frameType);
+        }
+
+        @Override
+        public int readAccess() {
+            return ACCESS_BITMAP;
+        }
+
+        @Override
+        public int writeAccess() {
+            return ACCESS_BITMAP;
+        }
+
+        @Override
+        public void syncTo(Backing backing) {
+            int access = backing.readAccess();
+            if ((access & ACCESS_BITMAP) != 0) {
+                mBitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
+            } else if ((access & ACCESS_BYTES) != 0) {
+                createBitmap();
+                ByteBuffer buffer = (ByteBuffer) backing.lock(ACCESS_BYTES);
+                mBitmap.copyPixelsFromBuffer(buffer);
+                buffer.rewind();
+            } else if ((access & ACCESS_TEXTURE) != 0) {
+                createBitmap();
+                RenderTarget renderTarget = (RenderTarget) backing.lock(ACCESS_RENDERTARGET);
+                mBitmap.copyPixelsFromBuffer(
+                        renderTarget.getPixelData(mDimensions[0], mDimensions[1]));
+            } else if ((access & ACCESS_ALLOCATION) != 0 && AllocationBacking.isSupported()) {
+                createBitmap();
+                syncToAllocationBacking(backing);
+            } else {
+                throw new RuntimeException("Cannot sync bytebuffer backing!");
+            }
+            backing.unlock();
+            mIsDirty = false;
+        }
+
+        @TargetApi(11)
+        private void syncToAllocationBacking(Backing backing) {
+            Allocation allocation = (Allocation) backing.lock(ACCESS_ALLOCATION);
+            allocation.copyTo(mBitmap);
+        }
+
+        @Override
+        public Object lock(int accessType) {
+            return mBitmap;
+        }
+
+        @Override
+        public int getType() {
+            return BACKING_BITMAP;
+        }
+
+        @Override
+        public boolean shouldCache() {
+            return false;
+        }
+
+        @Override
+        public boolean requiresGpu() {
+            return false;
+        }
+
+        @Override
+        public void destroy() {
+            // As we share the bitmap with other backings (such as object backings), we must not
+            // recycle it here.
+            mBitmap = null;
+        }
+
+        @Override
+        public int getSize() {
+            return 4 * mDimensions[0] * mDimensions[1];
+        }
+
+        @Override
+        public void setData(Object data) {
+            // We can assume that data will always be a Bitmap instance.
+            mBitmap = (Bitmap) data;
+        }
+
+        private void createBitmap() {
+            mBitmap = Bitmap.createBitmap(mDimensions[0], mDimensions[1], Bitmap.Config.ARGB_8888);
+        }
+    }
+
+    static class TextureBacking extends Backing {
+
+        private RenderTarget mRenderTarget = null;
+        private TextureSource mTexture = null;
+
+        @Override
+        public void allocate(FrameType frameType) {
+            assertImageCompatible(frameType);
+            mTexture = TextureSource.newTexture();
+        }
+
+        @Override
+        public int readAccess() {
+            return ACCESS_TEXTURE;
+        }
+
+        @Override
+        public int writeAccess() {
+            return ACCESS_RENDERTARGET;
+        }
+
+        @Override
+        public void syncTo(Backing backing) {
+            int access = backing.readAccess();
+            if ((access & ACCESS_BYTES) != 0) {
+                ByteBuffer pixels = (ByteBuffer) backing.lock(ACCESS_BYTES);
+                mTexture.allocateWithPixels(pixels, mDimensions[0], mDimensions[1]);
+            } else if ((access & ACCESS_BITMAP) != 0) {
+                Bitmap bitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
+                mTexture.allocateWithBitmapPixels(bitmap);
+            } else if ((access & ACCESS_TEXTURE) != 0) {
+                TextureSource texture = (TextureSource) backing.lock(ACCESS_TEXTURE);
+                int w = mDimensions[0];
+                int h = mDimensions[1];
+                ImageShader.renderTextureToTarget(texture, getRenderTarget(), w, h);
+            } else if ((access & ACCESS_ALLOCATION) != 0 && AllocationBacking.isSupported()) {
+                syncToAllocationBacking(backing);
+            } else {
+                throw new RuntimeException("Cannot sync bytebuffer backing!");
+            }
+            backing.unlock();
+            mIsDirty = false;
+        }
+
+        @TargetApi(11)
+        private void syncToAllocationBacking(Backing backing) {
+            Allocation allocation = (Allocation) backing.lock(ACCESS_ALLOCATION);
+            ByteBuffer pixels = ByteBuffer.allocateDirect(getSize());
+            allocation.copyTo(pixels.array());
+            mTexture.allocateWithPixels(pixels, mDimensions[0], mDimensions[1]);
+        }
+
+        @Override
+        public Object lock(int accessType) {
+            switch (accessType) {
+                case ACCESS_TEXTURE:
+                    return getTexture();
+
+                case ACCESS_RENDERTARGET:
+                    return getRenderTarget();
+
+                default:
+                    throw new RuntimeException("Illegal access to texture!");
+            }
+        }
+
+        @Override
+        public int getType() {
+            return BACKING_TEXTURE;
+        }
+
+        @Override
+        public boolean shouldCache() {
+            return true;
+        }
+
+        @Override
+        public boolean requiresGpu() {
+            return true;
+        }
+
+        @Override
+        public void destroy() {
+            if (mRenderTarget != null) {
+                mRenderTarget.release();
+            }
+            if (mTexture.isAllocated()) {
+                mTexture.release();
+            }
+        }
+
+        @Override
+        public int getSize() {
+            return 4 * mDimensions[0] * mDimensions[1];
+        }
+
+        private TextureSource getTexture() {
+            if (!mTexture.isAllocated()) {
+                mTexture.allocate(mDimensions[0], mDimensions[1]);
+            }
+            return mTexture;
+        }
+
+        private RenderTarget getRenderTarget() {
+            if (mRenderTarget == null) {
+                int w = mDimensions[0];
+                int h = mDimensions[1];
+                mRenderTarget = RenderTarget.currentTarget().forTexture(getTexture(), w, h);
+            }
+            return mRenderTarget;
+        }
+
+    }
+
+    static class ByteBufferBacking extends Backing {
+
+        ByteBuffer mBuffer = null;
+
+        @Override
+        public void allocate(FrameType frameType) {
+            int size = frameType.getElementSize();
+            for (int dim : mDimensions) {
+                size *= dim;
+            }
+            mBuffer = ByteBuffer.allocateDirect(size);
+        }
+
+        @Override
+        public int readAccess() {
+            return ACCESS_BYTES;
+        }
+
+        @Override
+        public int writeAccess() {
+            return ACCESS_BYTES;
+        }
+
+        @Override
+        public boolean requiresGpu() {
+            return false;
+        }
+
+        @Override
+        public void syncTo(Backing backing) {
+            int access = backing.readAccess();
+            if ((access & ACCESS_TEXTURE) != 0) {
+                RenderTarget target = (RenderTarget) backing.lock(ACCESS_RENDERTARGET);
+                GLToolbox.readTarget(target, mBuffer, mDimensions[0], mDimensions[1]);
+            } else if ((access & ACCESS_BITMAP) != 0) {
+                Bitmap bitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
+                bitmap.copyPixelsToBuffer(mBuffer);
+                mBuffer.rewind();
+            } else if ((access & ACCESS_BYTES) != 0) {
+                ByteBuffer otherBuffer = (ByteBuffer) backing.lock(ACCESS_BYTES);
+                mBuffer.put(otherBuffer);
+                otherBuffer.rewind();
+            } else if ((access & ACCESS_ALLOCATION) != 0 && AllocationBacking.isSupported()) {
+                syncToAllocationBacking(backing);
+            } else {
+                throw new RuntimeException("Cannot sync bytebuffer backing!");
+            }
+            backing.unlock();
+            mBuffer.rewind();
+            mIsDirty = false;
+        }
+
+        @TargetApi(11)
+        private void syncToAllocationBacking(Backing backing) {
+            Allocation allocation = (Allocation) backing.lock(ACCESS_ALLOCATION);
+            if (getElementId() == FrameType.ELEMENT_RGBA8888) {
+                byte[] bytes = mBuffer.array();
+                allocation.copyTo(bytes);
+            } else if (getElementId() == FrameType.ELEMENT_FLOAT32) {
+                float[] floats = new float[getSize() / 4];
+                allocation.copyTo(floats);
+                mBuffer.asFloatBuffer().put(floats);
+            } else {
+                throw new RuntimeException(
+                        "Trying to sync to an allocation with an unsupported element id: "
+                        + getElementId());
+            }
+        }
+
+        @Override
+        public Object lock(int accessType) {
+            return mBuffer.rewind();
+        }
+
+        @Override
+        public void unlock() {
+            mBuffer.rewind();
+        }
+
+        @Override
+        public int getType() {
+            return BACKING_BYTEBUFFER;
+        }
+
+        @Override
+        public boolean shouldCache() {
+            return true;
+        }
+
+        @Override
+        public void destroy() {
+            mBuffer = null;
+        }
+
+        @Override
+        public int getSize() {
+            return mBuffer.remaining();
+        }
+
+    }
+
+    @TargetApi(11)
+    static class AllocationBacking extends Backing {
+
+        private final RenderScript mRenderScript;
+        private Allocation mAllocation = null;
+
+        public AllocationBacking(RenderScript renderScript) {
+            mRenderScript = renderScript;
+        }
+
+        @Override
+        public void allocate(FrameType frameType) {
+            assertCompatible(frameType);
+
+            Element element = null;
+            switch (frameType.getElementId()) {
+                case FrameType.ELEMENT_RGBA8888:
+                    element = Element.RGBA_8888(mRenderScript);
+                    break;
+                case FrameType.ELEMENT_FLOAT32:
+                    element = Element.F32(mRenderScript);
+                    break;
+            }
+            Type.Builder imageTypeBuilder = new Type.Builder(mRenderScript, element);
+            imageTypeBuilder.setX(mDimensions.length >= 1 ? mDimensions[0] : 1);
+            imageTypeBuilder.setY(mDimensions.length == 2 ? mDimensions[1] : 1);
+            Type imageType = imageTypeBuilder.create();
+
+            mAllocation = Allocation.createTyped(mRenderScript, imageType);
+        }
+
+        @Override
+        public int readAccess() {
+            return ACCESS_ALLOCATION;
+        }
+
+        @Override
+        public int writeAccess() {
+            return ACCESS_ALLOCATION;
+        }
+
+        @Override
+        public boolean requiresGpu() {
+            return false;
+        }
+
+        @Override
+        public void syncTo(Backing backing) {
+            int access = backing.readAccess();
+            if ((access & ACCESS_TEXTURE) != 0) {
+                RenderTarget target = (RenderTarget) backing.lock(ACCESS_RENDERTARGET);
+                ByteBuffer pixels = ByteBuffer.allocateDirect(getSize());
+                GLToolbox.readTarget(target, pixels, mDimensions[0], mDimensions[1]);
+                mAllocation.copyFrom(pixels.array());
+            } else if ((access & ACCESS_BITMAP) != 0) {
+                Bitmap bitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
+                mAllocation.copyFrom(bitmap);
+            } else if ((access & ACCESS_BYTES) != 0) {
+                ByteBuffer buffer = (ByteBuffer) backing.lock(ACCESS_BYTES);
+                if (buffer.order() != ByteOrder.nativeOrder()) {
+                    throw new RuntimeException(
+                            "Trying to sync to the ByteBufferBacking with non-native byte order!");
+                }
+                byte[] bytes;
+                if (buffer.hasArray()) {
+                    bytes = buffer.array();
+                } else {
+                    bytes = new byte[getSize()];
+                    buffer.get(bytes);
+                    buffer.rewind();
+                }
+                mAllocation.copyFromUnchecked(bytes);
+            } else {
+                throw new RuntimeException("Cannot sync allocation backing!");
+            }
+            backing.unlock();
+            mIsDirty = false;
+        }
+
+        @Override
+        public Object lock(int accessType) {
+            return mAllocation;
+        }
+
+        @Override
+        public void unlock() {
+        }
+
+        @Override
+        public int getType() {
+            return BACKING_ALLOCATION;
+        }
+
+        @Override
+        public boolean shouldCache() {
+            return true;
+        }
+
+        @Override
+        public void destroy() {
+            if (mAllocation != null) {
+                mAllocation.destroy();
+                mAllocation = null;
+            }
+        }
+
+        @Override
+        public int getSize() {
+            int elementCount = 1;
+            for (int dim : mDimensions) {
+                elementCount *= dim;
+            }
+            return getElementSize() * elementCount;
+        }
+
+        public static boolean isSupported() {
+            return Build.VERSION.SDK_INT >= 11;
+        }
+
+        private void assertCompatible(FrameType type) {
+            // TODO: consider adding support for other data types.
+            if (type.getElementId() != FrameType.ELEMENT_RGBA8888
+                    && type.getElementId() != FrameType.ELEMENT_FLOAT32) {
+                throw new RuntimeException(
+                        "Cannot allocate allocation with a non-RGBA or non-float data type!");
+            }
+            if (mDimensions == null || mDimensions.length > 2) {
+                throw new RuntimeException(
+                        "Cannot create an allocation with more than 2 dimensions!");
+            }
+        }
+
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BranchFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BranchFilter.java
new file mode 100644
index 0000000..6e7c014
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BranchFilter.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public final class BranchFilter extends Filter {
+
+    private boolean mSynchronized = true;
+
+    public BranchFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    public BranchFilter(MffContext context, String name, boolean synced) {
+        super(context, name);
+        mSynchronized = synced;
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("input", Signature.PORT_REQUIRED, FrameType.any())
+            .addInputPort("synchronized", Signature.PORT_OPTIONAL,FrameType.single(boolean.class))
+            .disallowOtherInputs();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("input")) {
+            for (OutputPort outputPort : getConnectedOutputPorts()) {
+                port.attachToOutputPort(outputPort);
+            }
+        } else if (port.getName().equals("synchronized")) {
+            port.bindToFieldNamed("mSynchronized");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onOpen() {
+        updateSynchronization();
+    }
+
+    @Override
+    protected void onProcess() {
+        Frame inputFrame = getConnectedInputPort("input").pullFrame();
+        for (OutputPort outputPort : getConnectedOutputPorts()) {
+            if (outputPort.isAvailable()) {
+                outputPort.pushFrame(inputFrame);
+            }
+        }
+    }
+
+    private void updateSynchronization() {
+        if (mSynchronized) {
+            for (OutputPort port : getConnectedOutputPorts()) {
+                port.setWaitsUntilAvailable(true);
+            }
+        } else {
+            for (OutputPort port : getConnectedOutputPorts()) {
+                port.setWaitsUntilAvailable(false);
+            }
+        }
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BrightnessFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BrightnessFilter.java
new file mode 100644
index 0000000..5a70776
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/BrightnessFilter.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.media.filterpacks.image;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public class BrightnessFilter extends Filter {
+
+    private float mBrightness = 1.0f;
+    private ImageShader mShader;
+
+    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" +
+            "  if (brightness < 0.5) {\n" +
+            "    gl_FragColor = color * (2.0 * brightness);\n" +
+            "  } else {\n" +
+            "    vec4 diff = 1.0 - color;\n" +
+            "    gl_FragColor = color + diff * (2.0 * (brightness - 0.5));\n" +
+            "  }\n" +
+            "}\n";
+
+
+    public BrightnessFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("brightness", Signature.PORT_OPTIONAL, FrameType.single(float.class))
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("brightness")) {
+            port.bindToFieldNamed("mBrightness");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onPrepare() {
+        mShader = new ImageShader(mBrightnessShader);
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("image");
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        int[] dim = inputImage.getDimensions();
+        FrameImage2D outputImage = outPort.fetchAvailableFrame(dim).asFrameImage2D();
+        mShader.setUniformValue("brightness", mBrightness);
+        mShader.process(inputImage, outputImage);
+        outPort.pushFrame(outputImage);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CameraStreamer.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CameraStreamer.java
new file mode 100644
index 0000000..d1642fd
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CameraStreamer.java
@@ -0,0 +1,1906 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.PreviewCallback;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.opengl.GLES20;
+import android.os.Build.VERSION;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceView;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.microedition.khronos.egl.EGLContext;
+
+/**
+ * The CameraStreamer streams Frames from a camera to connected clients.
+ *
+ * There is one centralized CameraStreamer object per MffContext, and only one stream can be
+ * active at any time. The CameraStreamer acts as a Camera "server" that streams frames to any
+ * number of connected clients. Typically, these are CameraSource filters that are part of a
+ * graph, but other clients can be written as well.
+ */
+public class CameraStreamer {
+
+    /** Camera Facing: Don't Care: Picks any available camera. */
+    public static final int FACING_DONTCARE = 0;
+    /** Camera Facing: Front: Use the front facing camera. */
+    public static final int FACING_FRONT = 1;
+    /** Camera Facing: Back: Use the rear facing camera. */
+    public static final int FACING_BACK = 2;
+
+    /** How long the streamer should wait to acquire the camera before giving up. */
+    public static long MAX_CAMERA_WAIT_TIME = 5;
+
+    /**
+     * The global camera lock, that is closed when the camera is acquired by any CameraStreamer,
+     * and opened when a streamer is done using the camera.
+     */
+    static ReentrantLock mCameraLock = new ReentrantLock();
+
+    /** The Camera thread that grabs frames from the camera */
+    private CameraRunnable mCameraRunner = null;
+
+    private abstract class CamFrameHandler {
+        protected int mCameraWidth;
+        protected int mCameraHeight;
+        protected int mOutWidth;
+        protected int mOutHeight;
+        protected CameraRunnable mRunner;
+
+        /** Map of GLSL shaders (one for each target context) */
+        protected HashMap<EGLContext, ImageShader> mTargetShaders
+            = new HashMap<EGLContext, ImageShader>();
+
+        /** Map of target textures (one for each target context) */
+        protected HashMap<EGLContext, TextureSource> mTargetTextures
+            = new HashMap<EGLContext, TextureSource>();
+
+        /** Map of set of clients (one for each target context) */
+        protected HashMap<EGLContext, Set<FrameClient>> mContextClients
+            = new HashMap<EGLContext, Set<FrameClient>>();
+
+        /** List of clients that are consuming camera frames. */
+        protected Vector<FrameClient> mClients = new Vector<FrameClient>();
+
+        public void initWithRunner(CameraRunnable camRunner) {
+            mRunner = camRunner;
+        }
+
+        public void setCameraSize(int width, int height) {
+            mCameraWidth = width;
+            mCameraHeight = height;
+        }
+
+        public void registerClient(FrameClient client) {
+            EGLContext context = RenderTarget.currentContext();
+            Set<FrameClient> clientTargets = clientsForContext(context);
+            clientTargets.add(client);
+            mClients.add(client);
+            onRegisterClient(client, context);
+        }
+
+        public void unregisterClient(FrameClient client) {
+            EGLContext context = RenderTarget.currentContext();
+            Set<FrameClient> clientTargets = clientsForContext(context);
+            clientTargets.remove(client);
+            if (clientTargets.isEmpty()) {
+                onCleanupContext(context);
+            }
+            mClients.remove(client);
+        }
+
+        public abstract void setupServerFrame();
+        public abstract void updateServerFrame();
+        public abstract void grabFrame(FrameImage2D targetFrame);
+        public abstract void release();
+
+        public void onUpdateCameraOrientation(int orientation) {
+            if (orientation % 180 != 0) {
+                mOutWidth = mCameraHeight;
+                mOutHeight = mCameraWidth;
+            } else {
+                mOutWidth = mCameraWidth;
+                mOutHeight = mCameraHeight;
+            }
+        }
+
+        protected Set<FrameClient> clientsForContext(EGLContext context) {
+            Set<FrameClient> clients = mContextClients.get(context);
+            if (clients == null) {
+                clients = new HashSet<FrameClient>();
+                mContextClients.put(context, clients);
+            }
+            return clients;
+        }
+
+        protected void onRegisterClient(FrameClient client, EGLContext context) {
+        }
+
+        protected void onCleanupContext(EGLContext context) {
+            TextureSource texture = mTargetTextures.get(context);
+            ImageShader shader = mTargetShaders.get(context);
+            if (texture != null) {
+                texture.release();
+                mTargetTextures.remove(context);
+            }
+            if (shader != null) {
+                mTargetShaders.remove(context);
+            }
+        }
+
+        protected TextureSource textureForContext(EGLContext context) {
+            TextureSource texture = mTargetTextures.get(context);
+            if (texture == null) {
+                texture = createClientTexture();
+                mTargetTextures.put(context, texture);
+            }
+            return texture;
+        }
+
+        protected ImageShader shaderForContext(EGLContext context) {
+            ImageShader shader = mTargetShaders.get(context);
+            if (shader == null) {
+                shader = createClientShader();
+                mTargetShaders.put(context, shader);
+            }
+            return shader;
+        }
+
+        protected ImageShader createClientShader() {
+            return null;
+        }
+
+        protected TextureSource createClientTexture() {
+            return null;
+        }
+
+        public boolean isFrontMirrored() {
+            return true;
+        }
+    }
+
+    // Jellybean (and later) back-end
+    @TargetApi(16)
+    private class CamFrameHandlerJB extends CamFrameHandlerICS {
+
+        @Override
+        public void setupServerFrame() {
+            setupPreviewTexture(mRunner.mCamera);
+        }
+
+        @Override
+        public synchronized void updateServerFrame() {
+            updateSurfaceTexture();
+            informClients();
+        }
+
+        @Override
+        public synchronized void grabFrame(FrameImage2D targetFrame) {
+            TextureSource targetTex = TextureSource.newExternalTexture();
+            ImageShader copyShader = shaderForContext(RenderTarget.currentContext());
+            if (targetTex == null || copyShader == null) {
+                throw new RuntimeException("Attempting to grab camera frame from unknown "
+                    + "thread: " + Thread.currentThread() + "!");
+            }
+            mPreviewSurfaceTexture.attachToGLContext(targetTex.getTextureId());
+            updateTransform(copyShader);
+            updateShaderTargetRect(copyShader);
+            targetFrame.resize(new int[] { mOutWidth, mOutHeight });
+            copyShader.process(targetTex,
+                               targetFrame.lockRenderTarget(),
+                               mOutWidth,
+                               mOutHeight);
+            targetFrame.setTimestamp(mPreviewSurfaceTexture.getTimestamp());
+            targetFrame.unlock();
+            mPreviewSurfaceTexture.detachFromGLContext();
+            targetTex.release();
+        }
+
+        @Override
+        protected void updateShaderTargetRect(ImageShader shader) {
+            if ((mRunner.mActualFacing == FACING_FRONT) && mRunner.mFlipFront) {
+                shader.setTargetRect(1f, 1f, -1f, -1f);
+            } else {
+                shader.setTargetRect(0f, 1f, 1f, -1f);
+            }
+        }
+
+        @Override
+        protected void setupPreviewTexture(Camera camera) {
+            super.setupPreviewTexture(camera);
+            mPreviewSurfaceTexture.detachFromGLContext();
+        }
+
+        protected void updateSurfaceTexture() {
+            mPreviewSurfaceTexture.attachToGLContext(mPreviewTexture.getTextureId());
+            mPreviewSurfaceTexture.updateTexImage();
+            mPreviewSurfaceTexture.detachFromGLContext();
+        }
+
+        protected void informClients() {
+            synchronized (mClients) {
+                for (FrameClient client : mClients) {
+                    client.onCameraFrameAvailable();
+                }
+            }
+        }
+    }
+
+    // ICS (and later) back-end
+    @TargetApi(15)
+    private class CamFrameHandlerICS extends CamFrameHandler  {
+
+        protected static final String mCopyShaderSource =
+            "#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 camera transform matrix */
+        private float[] mCameraTransform = new float[16];
+
+        /** The texture the camera streams to */
+        protected TextureSource mPreviewTexture = null;
+        protected SurfaceTexture mPreviewSurfaceTexture = null;
+
+        /** Map of target surface textures (one for each target context) */
+        protected HashMap<EGLContext, SurfaceTexture> mTargetSurfaceTextures
+            = new HashMap<EGLContext, SurfaceTexture>();
+
+        /** Map of RenderTargets for client SurfaceTextures */
+        protected HashMap<SurfaceTexture, RenderTarget> mClientRenderTargets
+            = new HashMap<SurfaceTexture, RenderTarget>();
+
+        /** Server side copy shader */
+        protected ImageShader mCopyShader = null;
+
+        @Override
+        public synchronized void setupServerFrame() {
+            setupPreviewTexture(mRunner.mCamera);
+        }
+
+        @Override
+        public synchronized void updateServerFrame() {
+            mPreviewSurfaceTexture.updateTexImage();
+            distributeFrames();
+        }
+
+        @Override
+        public void onUpdateCameraOrientation(int orientation) {
+            super.onUpdateCameraOrientation(orientation);
+            mRunner.mCamera.setDisplayOrientation(orientation);
+            updateSurfaceTextureSizes();
+        }
+
+        @Override
+        public synchronized void onRegisterClient(FrameClient client, EGLContext context) {
+            final Set<FrameClient> clientTargets = clientsForContext(context);
+
+            // Make sure we have texture, shader, and surfacetexture setup for this context.
+            TextureSource clientTex = textureForContext(context);
+            ImageShader copyShader = shaderForContext(context);
+            SurfaceTexture surfTex = surfaceTextureForContext(context);
+
+            // Listen to client-side surface texture updates
+            surfTex.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
+                @Override
+                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                    for (FrameClient clientTarget : clientTargets) {
+                        clientTarget.onCameraFrameAvailable();
+                    }
+                }
+            });
+        }
+
+        @Override
+        public synchronized void grabFrame(FrameImage2D targetFrame) {
+            // Get the GL objects for the receiver's context
+            EGLContext clientContext = RenderTarget.currentContext();
+            TextureSource clientTex = textureForContext(clientContext);
+            ImageShader copyShader = shaderForContext(clientContext);
+            SurfaceTexture surfTex = surfaceTextureForContext(clientContext);
+            if (clientTex == null || copyShader == null || surfTex == null) {
+                throw new RuntimeException("Attempting to grab camera frame from unknown "
+                    + "thread: " + Thread.currentThread() + "!");
+            }
+
+            // Copy from client ST to client tex
+            surfTex.updateTexImage();
+            targetFrame.resize(new int[] { mOutWidth, mOutHeight });
+            copyShader.process(clientTex,
+                               targetFrame.lockRenderTarget(),
+                               mOutWidth,
+                               mOutHeight);
+
+            targetFrame.setTimestamp(mPreviewSurfaceTexture.getTimestamp());
+            targetFrame.unlock();
+        }
+
+        @Override
+        public synchronized void release() {
+            if (mPreviewTexture != null) {
+                mPreviewTexture.release();
+                mPreviewTexture = null;
+            }
+            if (mPreviewSurfaceTexture != null) {
+                mPreviewSurfaceTexture.release();
+                mPreviewSurfaceTexture = null;
+            }
+        }
+
+        @Override
+        protected ImageShader createClientShader() {
+            return new ImageShader(mCopyShaderSource);
+        }
+
+        @Override
+        protected TextureSource createClientTexture() {
+            return TextureSource.newExternalTexture();
+        }
+
+        protected void distributeFrames() {
+            updateTransform(getCopyShader());
+            updateShaderTargetRect(getCopyShader());
+
+            for (SurfaceTexture clientTexture : mTargetSurfaceTextures.values()) {
+                RenderTarget clientTarget = renderTargetFor(clientTexture);
+                clientTarget.focus();
+                getCopyShader().process(mPreviewTexture,
+                                        clientTarget,
+                                        mOutWidth,
+                                        mOutHeight);
+                GLToolbox.checkGlError("distribute frames");
+                clientTarget.swapBuffers();
+            }
+        }
+
+        protected RenderTarget renderTargetFor(SurfaceTexture surfaceTex) {
+            RenderTarget target = mClientRenderTargets.get(surfaceTex);
+            if (target == null) {
+                target = RenderTarget.currentTarget().forSurfaceTexture(surfaceTex);
+                mClientRenderTargets.put(surfaceTex, target);
+            }
+            return target;
+        }
+
+        protected void setupPreviewTexture(Camera camera) {
+            if (mPreviewTexture == null) {
+                mPreviewTexture = TextureSource.newExternalTexture();
+            }
+            if (mPreviewSurfaceTexture == null) {
+                mPreviewSurfaceTexture = new SurfaceTexture(mPreviewTexture.getTextureId());
+                try {
+                    camera.setPreviewTexture(mPreviewSurfaceTexture);
+                } catch (IOException e) {
+                    throw new RuntimeException("Could not bind camera surface texture: " +
+                                               e.getMessage() + "!");
+                }
+                mPreviewSurfaceTexture.setOnFrameAvailableListener(mOnCameraFrameListener);
+            }
+        }
+
+        protected ImageShader getCopyShader() {
+            if (mCopyShader == null) {
+                mCopyShader = new ImageShader(mCopyShaderSource);
+            }
+            return mCopyShader;
+        }
+
+        protected SurfaceTexture surfaceTextureForContext(EGLContext context) {
+            SurfaceTexture surfTex = mTargetSurfaceTextures.get(context);
+            if (surfTex == null) {
+                TextureSource texture = textureForContext(context);
+                if (texture != null) {
+                    surfTex = new SurfaceTexture(texture.getTextureId());
+                    surfTex.setDefaultBufferSize(mOutWidth, mOutHeight);
+                    mTargetSurfaceTextures.put(context, surfTex);
+                }
+            }
+            return surfTex;
+        }
+
+        protected void updateShaderTargetRect(ImageShader shader) {
+            if ((mRunner.mActualFacing == FACING_FRONT) && mRunner.mFlipFront) {
+                shader.setTargetRect(1f, 0f, -1f, 1f);
+            } else {
+                shader.setTargetRect(0f, 0f, 1f, 1f);
+            }
+        }
+
+        protected synchronized void updateSurfaceTextureSizes() {
+            for (SurfaceTexture clientTexture : mTargetSurfaceTextures.values()) {
+                clientTexture.setDefaultBufferSize(mOutWidth, mOutHeight);
+            }
+        }
+
+        protected void updateTransform(ImageShader shader) {
+            mPreviewSurfaceTexture.getTransformMatrix(mCameraTransform);
+            shader.setSourceTransform(mCameraTransform);
+        }
+
+        @Override
+        protected void onCleanupContext(EGLContext context) {
+            super.onCleanupContext(context);
+            SurfaceTexture surfaceTex = mTargetSurfaceTextures.get(context);
+            if (surfaceTex != null) {
+                surfaceTex.release();
+                mTargetSurfaceTextures.remove(context);
+            }
+        }
+
+        protected SurfaceTexture.OnFrameAvailableListener mOnCameraFrameListener =
+                new SurfaceTexture.OnFrameAvailableListener() {
+            @Override
+            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                mRunner.signalNewFrame();
+            }
+        };
+    }
+
+    // Gingerbread (and later) back-end
+    @TargetApi(9)
+    private final class CamFrameHandlerGB extends CamFrameHandler  {
+
+        private SurfaceView mSurfaceView;
+        private byte[] mFrameBufferFront;
+        private byte[] mFrameBufferBack;
+        private boolean mWriteToBack = true;
+        private float[] mTargetCoords = new float[] { 0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f };
+        final Object mBufferLock = new Object();
+
+        private String mNV21ToRGBAFragment =
+            "precision mediump float;\n" +
+            "\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_y_texcoord;\n" +
+            "varying vec2 v_vu_texcoord;\n" +
+            "varying vec2 v_pixcoord;\n" +
+            "\n" +
+            "vec3 select(vec4 yyyy, vec4 vuvu, int s) {\n" +
+            "  if (s == 0) {\n" +
+            "    return vec3(yyyy.r, vuvu.g, vuvu.r);\n" +
+            "  } else if (s == 1) {\n" +
+            "    return vec3(yyyy.g, vuvu.g, vuvu.r);\n" +
+            " } else if (s == 2) {\n" +
+            "    return vec3(yyyy.b, vuvu.a, vuvu.b);\n" +
+            "  } else  {\n" +
+            "    return vec3(yyyy.a, vuvu.a, vuvu.b);\n" +
+            "  }\n" +
+            "}\n" +
+            "\n" +
+            "vec3 yuv2rgb(vec3 yuv) {\n" +
+            "  mat4 conversion = mat4(1.0,  0.0,    1.402, -0.701,\n" +
+            "                         1.0, -0.344, -0.714,  0.529,\n" +
+            "                         1.0,  1.772,  0.0,   -0.886,\n" +
+            "                         0, 0, 0, 0);" +
+            "  return (vec4(yuv, 1.0) * conversion).rgb;\n" +
+            "}\n" +
+            "\n" +
+            "void main() {\n" +
+            "  vec4 yyyy = texture2D(tex_sampler_0, v_y_texcoord);\n" +
+            "  vec4 vuvu = texture2D(tex_sampler_0, v_vu_texcoord);\n" +
+            "  int s = int(mod(floor(v_pixcoord.x), 4.0));\n" +
+            "  vec3 yuv = select(yyyy, vuvu, s);\n" +
+            "  vec3 rgb = yuv2rgb(yuv);\n" +
+            "  gl_FragColor = vec4(rgb, 1.0);\n" +
+            "}";
+
+        private String mNV21ToRGBAVertex =
+            "attribute vec4 a_position;\n" +
+            "attribute vec2 a_y_texcoord;\n" +
+            "attribute vec2 a_vu_texcoord;\n" +
+            "attribute vec2 a_pixcoord;\n" +
+            "varying vec2 v_y_texcoord;\n" +
+            "varying vec2 v_vu_texcoord;\n" +
+            "varying vec2 v_pixcoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = a_position;\n" +
+            "  v_y_texcoord = a_y_texcoord;\n" +
+            "  v_vu_texcoord = a_vu_texcoord;\n" +
+            "  v_pixcoord = a_pixcoord;\n" +
+            "}\n";
+
+        private byte[] readBuffer() {
+            synchronized (mBufferLock) {
+                return mWriteToBack ? mFrameBufferFront : mFrameBufferBack;
+            }
+        }
+
+        private byte[] writeBuffer() {
+            synchronized (mBufferLock) {
+                return mWriteToBack ? mFrameBufferBack : mFrameBufferFront;
+            }
+        }
+
+        private synchronized void swapBuffers() {
+            synchronized (mBufferLock) {
+                mWriteToBack = !mWriteToBack;
+            }
+        }
+
+        private PreviewCallback mPreviewCallback = new PreviewCallback() {
+
+            @Override
+            public void onPreviewFrame(byte[] data, Camera camera) {
+                swapBuffers();
+                camera.addCallbackBuffer(writeBuffer());
+                mRunner.signalNewFrame();
+            }
+
+        };
+
+        @Override
+        public void setupServerFrame() {
+            checkCameraDimensions();
+            Camera camera = mRunner.mCamera;
+            int bufferSize = mCameraWidth * (mCameraHeight + mCameraHeight/2);
+            mFrameBufferFront = new byte[bufferSize];
+            mFrameBufferBack = new byte[bufferSize];
+            camera.addCallbackBuffer(writeBuffer());
+            camera.setPreviewCallbackWithBuffer(mPreviewCallback);
+            SurfaceView previewDisplay = getPreviewDisplay();
+            if (previewDisplay != null) {
+                try {
+                    camera.setPreviewDisplay(previewDisplay.getHolder());
+                } catch (IOException e) {
+                    throw new RuntimeException("Could not start camera with given preview " +
+                            "display!");
+                }
+            }
+        }
+
+        private void checkCameraDimensions() {
+            if (mCameraWidth % 4 != 0) {
+                throw new RuntimeException("Camera width must be a multiple of 4!");
+            } else if (mCameraHeight % 2 != 0) {
+                throw new RuntimeException("Camera height must be a multiple of 2!");
+            }
+        }
+
+        @Override
+        public void updateServerFrame() {
+            // Server frame has been updated already, simply inform clients here.
+            informClients();
+        }
+
+        @Override
+        public void grabFrame(FrameImage2D targetFrame) {
+            EGLContext clientContext = RenderTarget.currentContext();
+
+            // Copy camera data to the client YUV texture
+            TextureSource clientTex = textureForContext(clientContext);
+            int texWidth = mCameraWidth / 4;
+            int texHeight = mCameraHeight + mCameraHeight / 2;
+            synchronized(mBufferLock) {    // Don't swap buffers while we are reading
+                ByteBuffer pixels = ByteBuffer.wrap(readBuffer());
+                clientTex.allocateWithPixels(pixels, texWidth, texHeight);
+            }
+            clientTex.setParameter(GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+            clientTex.setParameter(GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+
+            // Setup the YUV-2-RGBA shader
+            ImageShader transferShader = shaderForContext(clientContext);
+            transferShader.setTargetCoords(mTargetCoords);
+            updateShaderPixelSize(transferShader);
+
+            // Convert pixels into target frame
+            targetFrame.resize(new int[] { mOutWidth, mOutHeight });
+            transferShader.process(clientTex,
+                    targetFrame.lockRenderTarget(),
+                    mOutWidth,
+                    mOutHeight);
+            targetFrame.unlock();
+        }
+
+        @Override
+        public void onUpdateCameraOrientation(int orientation) {
+            super.onUpdateCameraOrientation(orientation);
+            if ((mRunner.mActualFacing == FACING_FRONT) && mRunner.mFlipFront) {
+                switch (orientation) {
+                    case 0:
+                        mTargetCoords = new float[] { 1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f };
+                        break;
+                    case 90:
+                        mTargetCoords = new float[] { 0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f };
+                        break;
+                    case 180:
+                        mTargetCoords = new float[] { 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f };
+                        break;
+                    case 270:
+                        mTargetCoords = new float[] { 1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f };
+                        break;
+                }
+            } else {
+                switch (orientation) {
+                    case 0:
+                        mTargetCoords = new float[] { 0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f };
+                        break;
+                    case 90:
+                        mTargetCoords = new float[] { 1f, 0f, 1f, 1f, 0f, 0f, 0f, 1f };
+                        break;
+                    case 180:
+                        mTargetCoords = new float[] { 1f, 1f, 0f, 1f, 1f, 0f, 0f, 0f };
+                        break;
+                    case 270:
+                        mTargetCoords = new float[] { 0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f };
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public void release() {
+            mFrameBufferBack = null;
+            mFrameBufferFront = null;
+        }
+
+        @Override
+        public boolean isFrontMirrored() {
+            return false;
+        }
+
+        @Override
+        protected ImageShader createClientShader() {
+            ImageShader shader = new ImageShader(mNV21ToRGBAVertex, mNV21ToRGBAFragment);
+            // TODO: Make this a VBO
+            float[] yCoords = new float[] {
+                    0f, 0f,
+                    1f, 0f,
+                    0f, 2f / 3f,
+                    1f, 2f / 3f };
+            float[] uvCoords = new float[] {
+                    0f, 2f / 3f,
+                    1f, 2f / 3f,
+                    0f, 1f,
+                    1f, 1f };
+            shader.setAttributeValues("a_y_texcoord", yCoords, 2);
+            shader.setAttributeValues("a_vu_texcoord", uvCoords, 2);
+            return shader;
+        }
+
+        @Override
+        protected TextureSource createClientTexture() {
+            TextureSource texture = TextureSource.newTexture();
+            texture.setParameter(GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+            texture.setParameter(GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+            return texture;
+        }
+
+        private void updateShaderPixelSize(ImageShader shader) {
+            float[] pixCoords = new float[] {
+                    0f, 0f,
+                    mCameraWidth, 0f,
+                    0f, mCameraHeight,
+                    mCameraWidth, mCameraHeight };
+            shader.setAttributeValues("a_pixcoord", pixCoords, 2);
+        }
+
+        private SurfaceView getPreviewDisplay() {
+            if (mSurfaceView == null) {
+                mSurfaceView = mRunner.getContext().getDummySurfaceView();
+            }
+            return mSurfaceView;
+        }
+
+        private void informClients() {
+            synchronized (mClients) {
+                for (FrameClient client : mClients) {
+                    client.onCameraFrameAvailable();
+                }
+            }
+        }
+    }
+
+    private static class State {
+        public static final int STATE_RUNNING = 1;
+        public static final int STATE_STOPPED = 2;
+        public static final int STATE_HALTED = 3;
+
+        private AtomicInteger mCurrent = new AtomicInteger(STATE_STOPPED);
+
+        public int current() {
+            return mCurrent.get();
+        }
+
+        public void set(int newState) {
+            mCurrent.set(newState);
+        }
+    }
+
+    private static class Event {
+        public static final int START = 1;
+        public static final int FRAME = 2;
+        public static final int STOP = 3;
+        public static final int HALT = 4;
+        public static final int RESTART = 5;
+        public static final int UPDATE = 6;
+        public static final int TEARDOWN = 7;
+
+        public int code;
+
+        public Event(int code) {
+            this.code = code;
+        }
+    }
+
+    private final class CameraRunnable implements Runnable {
+
+        /** On slower devices the event queue can easily fill up. We bound the queue to this. */
+        private final static int MAX_EVENTS = 32;
+
+        /** The runner's state */
+        private State mState = new State();
+
+        /** The CameraRunner's event queue */
+        private LinkedBlockingQueue<Event> mEventQueue = new LinkedBlockingQueue<Event>(MAX_EVENTS);
+
+        /** The requested FPS */
+        private int mRequestedFramesPerSec = 30;
+
+        /** The actual FPS */
+        private int mActualFramesPerSec = 0;
+
+        /** The requested preview width and height */
+        private int mRequestedPreviewWidth = 640;
+        private int mRequestedPreviewHeight = 480;
+
+        /** The requested picture width and height */
+        private int mRequestedPictureWidth = 640;
+        private int mRequestedPictureHeight = 480;
+
+        /** The actual camera width and height */
+        private int[] mActualDims = null;
+
+        /** The requested facing */
+        private int mRequestedFacing = FACING_DONTCARE;
+
+        /** The actual facing */
+        private int mActualFacing = FACING_DONTCARE;
+
+        /** Whether to horizontally flip the front facing camera */
+        private boolean mFlipFront = true;
+
+        /** The display the camera streamer is bound to. */
+        private Display mDisplay = null;
+
+        /** The camera and screen orientation. */
+        private int mCamOrientation = 0;
+        private int mOrientation = -1;
+
+        /** The camera rotation (used for capture). */
+        private int mCamRotation = 0;
+
+        /** The camera flash mode */
+        private String mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
+
+        /** The camera object */
+        private Camera mCamera = null;
+
+        private MediaRecorder mRecorder = null;
+
+        /** The ID of the currently used camera */
+        int mCamId = 0;
+
+        /** The platform-dependent camera frame handler. */
+        private CamFrameHandler mCamFrameHandler = null;
+
+        /** The set of camera listeners. */
+        private Set<CameraListener> mCamListeners = new HashSet<CameraListener>();
+
+        private ReentrantLock mCameraReadyLock = new ReentrantLock(true);
+        // mCameraReady condition is used when waiting for the camera getting ready.
+        private Condition mCameraReady = mCameraReadyLock.newCondition();
+        // external camera lock used to provide the capability of external camera access.
+        private ExternalCameraLock mExternalCameraLock = new ExternalCameraLock();
+
+        private RenderTarget mRenderTarget;
+        private MffContext mContext;
+
+        /**
+         *  This provides the capability of locking and unlocking from different threads.
+         *  The thread will wait until the lock state is idle. Any thread can wake up
+         *  a waiting thread by calling unlock (i.e. signal), provided that unlock
+         *  are called using the same context when lock was called. Using context prevents
+         *  from rogue usage of unlock.
+         */
+        private class ExternalCameraLock {
+            public static final int IDLE = 0;
+            public static final int IN_USE = 1;
+            private int mLockState = IDLE;
+            private Object mLockContext;
+            private final ReentrantLock mLock = new ReentrantLock(true);
+            private final Condition mInUseLockCondition= mLock.newCondition();
+
+            public boolean lock(Object context) {
+                if (context == null) {
+                    throw new RuntimeException("Null context when locking");
+                }
+                mLock.lock();
+                if (mLockState == IN_USE) {
+                    try {
+                        mInUseLockCondition.await();
+                    } catch (InterruptedException e) {
+                        return false;
+                    }
+                }
+                mLockState = IN_USE;
+                mLockContext = context;
+                mLock.unlock();
+                return true;
+            }
+
+            public void unlock(Object context) {
+                mLock.lock();
+                if (mLockState != IN_USE) {
+                    throw new RuntimeException("Not in IN_USE state");
+                }
+                if (context != mLockContext) {
+                    throw new RuntimeException("Lock is not owned by this context");
+                }
+                mLockState = IDLE;
+                mLockContext = null;
+                mInUseLockCondition.signal();
+                mLock.unlock();
+            }
+        }
+
+        public CameraRunnable(MffContext context) {
+            mContext = context;
+            createCamFrameHandler();
+            mCamFrameHandler.initWithRunner(this);
+            launchThread();
+        }
+
+        public MffContext getContext() {
+            return mContext;
+        }
+
+        public void loop() {
+            while (true) {
+                try {
+                    Event event = nextEvent();
+                    if (event == null) continue;
+                    switch (event.code) {
+                        case Event.START:
+                            onStart();
+                            break;
+                        case Event.STOP:
+                            onStop();
+                            break;
+                        case Event.FRAME:
+                            onFrame();
+                            break;
+                        case Event.HALT:
+                            onHalt();
+                            break;
+                        case Event.RESTART:
+                            onRestart();
+                            break;
+                        case Event.UPDATE:
+                            onUpdate();
+                            break;
+                        case Event.TEARDOWN:
+                            onTearDown();
+                            break;
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            loop();
+        }
+
+        public void signalNewFrame() {
+            pushEvent(Event.FRAME, false);
+        }
+
+        public void pushEvent(int eventId, boolean required) {
+            try {
+                if (required) {
+                    mEventQueue.put(new Event(eventId));
+                } else {
+                    mEventQueue.offer(new Event(eventId));
+                }
+            } catch (InterruptedException e) {
+                // We should never get here (as we do not limit capacity in the queue), but if
+                // we do, we log an error.
+                Log.e("CameraStreamer", "Dropping event " + eventId + "!");
+            }
+        }
+
+        public void launchThread() {
+            Thread cameraThread = new Thread(this);
+            cameraThread.start();
+        }
+
+        @Deprecated
+        public Camera getCamera() {
+            synchronized (mState) {
+                return mCamera;
+            }
+        }
+
+        public Camera lockCamera(Object context) {
+            mExternalCameraLock.lock(context);
+            /**
+             * since lockCamera can happen right after closeCamera,
+             * the camera handle can be null, wait until valid handle
+             * is acquired.
+             */
+            while (mCamera == null) {
+                mExternalCameraLock.unlock(context);
+                mCameraReadyLock.lock();
+                try {
+                    mCameraReady.await();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException("Condition interrupted", e);
+                }
+                mCameraReadyLock.unlock();
+                mExternalCameraLock.lock(context);
+            }
+            return mCamera;
+        }
+
+        public void unlockCamera(Object context) {
+            mExternalCameraLock.unlock(context);
+        }
+
+        public int getCurrentCameraId() {
+            synchronized (mState) {
+                return mCamId;
+            }
+        }
+
+        public boolean isRunning() {
+            return mState.current() != State.STATE_STOPPED;
+        }
+
+        public void addListener(CameraListener listener) {
+            synchronized (mCamListeners) {
+                mCamListeners.add(listener);
+            }
+        }
+
+        public void removeListener(CameraListener listener) {
+            synchronized (mCamListeners) {
+                mCamListeners.remove(listener);
+            }
+        }
+
+        public synchronized void bindToDisplay(Display display) {
+            mDisplay = display;
+        }
+
+        public synchronized void setDesiredPreviewSize(int width, int height) {
+            if (width != mRequestedPreviewWidth || height != mRequestedPreviewHeight) {
+                mRequestedPreviewWidth = width;
+                mRequestedPreviewHeight = height;
+                onParamsUpdated();
+            }
+        }
+
+        public synchronized void setDesiredPictureSize(int width, int height) {
+            if (width != mRequestedPictureWidth || height != mRequestedPictureHeight) {
+                mRequestedPictureWidth = width;
+                mRequestedPictureHeight = height;
+                onParamsUpdated();
+            }
+        }
+
+        public synchronized void setDesiredFrameRate(int fps) {
+            if (fps != mRequestedFramesPerSec) {
+                mRequestedFramesPerSec = fps;
+                onParamsUpdated();
+            }
+        }
+
+        public synchronized void setFacing(int facing) {
+            if (facing != mRequestedFacing) {
+                switch (facing) {
+                    case FACING_DONTCARE:
+                    case FACING_FRONT:
+                    case FACING_BACK:
+                        mRequestedFacing = facing;
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unknown facing value '" + facing
+                            + "' passed to setFacing!");
+                }
+                onParamsUpdated();
+            }
+        }
+
+        public synchronized void setFlipFrontCamera(boolean flipFront) {
+            if (mFlipFront != flipFront) {
+                mFlipFront = flipFront;
+            }
+        }
+
+        public synchronized void setFlashMode(String flashMode) {
+            if (!flashMode.equals(mFlashMode)) {
+                mFlashMode = flashMode;
+                onParamsUpdated();
+            }
+        }
+
+        public synchronized int getCameraFacing() {
+            return mActualFacing;
+        }
+
+        public synchronized int getCameraRotation() {
+            return mCamRotation;
+        }
+
+        public synchronized boolean supportsHardwareFaceDetection() {
+            //return mCamFrameHandler.supportsHardwareFaceDetection();
+            // TODO
+            return true;
+        }
+
+        public synchronized int getCameraWidth() {
+            return (mActualDims != null) ? mActualDims[0] : 0;
+        }
+
+        public synchronized int getCameraHeight() {
+            return (mActualDims != null) ? mActualDims[1] : 0;
+        }
+
+        public synchronized int getCameraFrameRate() {
+            return mActualFramesPerSec;
+        }
+
+        public synchronized String getFlashMode() {
+            return mCamera.getParameters().getFlashMode();
+        }
+
+        public synchronized boolean canStart() {
+            // If we can get a camera id without error we should be able to start.
+            try {
+                getCameraId();
+            } catch (RuntimeException e) {
+                return false;
+            }
+            return true;
+        }
+
+        public boolean grabFrame(FrameImage2D targetFrame) {
+            // Make sure we stay in state running while we are grabbing the frame.
+            synchronized (mState) {
+                if (mState.current() != State.STATE_RUNNING) {
+                    return false;
+                }
+                // we may not have the camera ready, this might happen when in the middle
+                // of switching camera.
+                if (mCamera == null) {
+                    return false;
+                }
+                mCamFrameHandler.grabFrame(targetFrame);
+                return true;
+            }
+        }
+
+        public CamFrameHandler getCamFrameHandler() {
+            return mCamFrameHandler;
+        }
+
+        private void onParamsUpdated() {
+            pushEvent(Event.UPDATE, true);
+        }
+
+        private Event nextEvent() {
+            try {
+                return mEventQueue.take();
+            } catch (InterruptedException e) {
+                // Ignore and keep going.
+                Log.w("GraphRunner", "Event queue processing was interrupted.");
+                return null;
+            }
+        }
+
+        private void onStart() {
+            if (mState.current() == State.STATE_STOPPED) {
+                mState.set(State.STATE_RUNNING);
+                getRenderTarget().focus();
+                openCamera();
+            }
+        }
+
+        private void onStop() {
+            if (mState.current() == State.STATE_RUNNING) {
+                closeCamera();
+                RenderTarget.focusNone();
+            }
+            // Set state to stop (halted becomes stopped).
+            mState.set(State.STATE_STOPPED);
+        }
+
+        private void onHalt() {
+            // Only halt if running. Stopped overrides halt.
+            if (mState.current() == State.STATE_RUNNING) {
+                closeCamera();
+                RenderTarget.focusNone();
+                mState.set(State.STATE_HALTED);
+            }
+        }
+
+        private void onRestart() {
+            // Only restart if halted
+            if (mState.current() == State.STATE_HALTED) {
+                mState.set(State.STATE_RUNNING);
+                getRenderTarget().focus();
+                openCamera();
+            }
+        }
+
+        private void onUpdate() {
+            if (mState.current() == State.STATE_RUNNING) {
+                pushEvent(Event.STOP, true);
+                pushEvent(Event.START, true);
+            }
+        }
+        private void onFrame() {
+            if (mState.current() == State.STATE_RUNNING) {
+                updateRotation();
+                mCamFrameHandler.updateServerFrame();
+            }
+        }
+
+        private void onTearDown() {
+            if (mState.current() == State.STATE_STOPPED) {
+                // Remove all listeners. This will release their resources
+                for (CameraListener listener : mCamListeners) {
+                    removeListener(listener);
+                }
+                mCamListeners.clear();
+            } else {
+                Log.e("CameraStreamer", "Could not tear-down CameraStreamer as camera still "
+                        + "seems to be running!");
+            }
+        }
+
+        private void createCamFrameHandler() {
+            // TODO: For now we simply assert that OpenGL is supported. Later on, we should add
+            // a CamFrameHandler that does not depend on OpenGL.
+            getContext().assertOpenGLSupported();
+            if (VERSION.SDK_INT >= 16) {
+                mCamFrameHandler = new CamFrameHandlerJB();
+            } else if (VERSION.SDK_INT >= 15) {
+                mCamFrameHandler = new CamFrameHandlerICS();
+            } else {
+                mCamFrameHandler = new CamFrameHandlerGB();
+            }
+        }
+
+        private void updateRotation() {
+            if (mDisplay != null) {
+                updateDisplayRotation(mDisplay.getRotation());
+            }
+        }
+
+        private synchronized void updateDisplayRotation(int rotation) {
+            switch (rotation) {
+                case Surface.ROTATION_0:
+                    onUpdateOrientation(0);
+                    break;
+                case Surface.ROTATION_90:
+                    onUpdateOrientation(90);
+                    break;
+                case Surface.ROTATION_180:
+                    onUpdateOrientation(180);
+                    break;
+                case Surface.ROTATION_270:
+                    onUpdateOrientation(270);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported display rotation constant! Use "
+                        + "one of the Surface.ROTATION_ constants!");
+            }
+        }
+
+        private RenderTarget getRenderTarget() {
+            if (mRenderTarget == null) {
+                mRenderTarget = RenderTarget.newTarget(1, 1);
+            }
+            return mRenderTarget;
+        }
+
+        private void updateCamera() {
+            synchronized (mState) {
+                mCamId = getCameraId();
+                updateCameraOrientation(mCamId);
+                mCamera = Camera.open(mCamId);
+                initCameraParameters();
+            }
+        }
+
+        private void updateCameraOrientation(int camId) {
+            CameraInfo cameraInfo = new CameraInfo();
+            Camera.getCameraInfo(camId, cameraInfo);
+            mCamOrientation = cameraInfo.orientation;
+            mOrientation = -1;  // Forces recalculation to match display
+            mActualFacing = (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT)
+                ? FACING_FRONT
+                : FACING_BACK;
+        }
+
+        private int getCameraId() {
+            int camCount = Camera.getNumberOfCameras();
+            if (camCount == 0) {
+                throw new RuntimeException("Device does not have any cameras!");
+            } else if (mRequestedFacing == FACING_DONTCARE) {
+                // Simply return first camera if mRequestedFacing is don't care
+                return 0;
+            }
+
+            // Attempt to find requested camera
+            boolean useFrontCam = (mRequestedFacing == FACING_FRONT);
+            CameraInfo cameraInfo = new CameraInfo();
+            for (int i = 0; i < camCount; ++i) {
+                Camera.getCameraInfo(i, cameraInfo);
+                if ((cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) == useFrontCam) {
+                    return i;
+                }
+            }
+            throw new RuntimeException("Could not find a camera facing (" + mRequestedFacing
+                    + ")!");
+        }
+
+        private void initCameraParameters() {
+            Camera.Parameters params = mCamera.getParameters();
+
+            // Find closest preview size
+            mActualDims =
+                findClosestPreviewSize(mRequestedPreviewWidth, mRequestedPreviewHeight, params);
+            mCamFrameHandler.setCameraSize(mActualDims[0], mActualDims[1]);
+            params.setPreviewSize(mActualDims[0], mActualDims[1]);
+            // Find closest picture size
+            int[] dims =
+                findClosestPictureSize(mRequestedPictureWidth, mRequestedPictureHeight, params);
+            params.setPictureSize(dims[0], dims[1]);
+
+            // Find closest FPS
+            int closestRange[] = findClosestFpsRange(mRequestedFramesPerSec, params);
+            params.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                                      closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+
+            // Set flash mode (if supported)
+            if (params.getFlashMode() != null) {
+                params.setFlashMode(mFlashMode);
+            }
+
+            mCamera.setParameters(params);
+        }
+
+        private int[] findClosestPreviewSize(int width, int height, Camera.Parameters parameters) {
+            List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
+            return findClosestSizeFromList(width, height, previewSizes);
+        }
+
+        private int[] findClosestPictureSize(int width, int height, Camera.Parameters parameters) {
+            List<Camera.Size> pictureSizes = parameters.getSupportedPictureSizes();
+            return findClosestSizeFromList(width, height, pictureSizes);
+        }
+
+        private int[] findClosestSizeFromList(int width, int height, List<Camera.Size> sizes) {
+            int closestWidth = -1;
+            int closestHeight = -1;
+            int smallestWidth = sizes.get(0).width;
+            int smallestHeight =  sizes.get(0).height;
+            for (Camera.Size size : sizes) {
+                // 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;
+            }
+            int[] closestSize = {closestWidth, closestHeight};
+            return closestSize;
+        }
+
+        private int[] findClosestFpsRange(int fps, Camera.Parameters params) {
+            List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
+            int[] closestRange = supportedFpsRanges.get(0);
+            int fpsk = fps * 1000;
+            int minDiff = 1000000;
+            for (int[] range : supportedFpsRanges) {
+                int low = range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+                int high = range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+                if (low <= fpsk && high >= fpsk) {
+                    int diff = (fpsk - low) + (high - fpsk);
+                    if (diff < minDiff) {
+                        closestRange = range;
+                        minDiff = diff;
+                    }
+                }
+            }
+            mActualFramesPerSec = closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000;
+            return closestRange;
+        }
+
+        private void onUpdateOrientation(int orientation) {
+            // First we calculate the camera rotation.
+            int rotation = (mActualFacing == FACING_FRONT)
+                    ? (mCamOrientation + orientation) % 360
+                    : (mCamOrientation - orientation + 360) % 360;
+            if (rotation != mCamRotation) {
+                synchronized (this) {
+                    mCamRotation = rotation;
+                }
+            }
+
+            // We compensate for mirroring in the orientation. This differs from the rotation,
+            // where we are invariant to mirroring.
+            int fixedOrientation = rotation;
+            if (mActualFacing == FACING_FRONT && mCamFrameHandler.isFrontMirrored()) {
+                fixedOrientation = (360 - rotation) % 360;  // compensate the mirror
+            }
+            if (mOrientation != fixedOrientation) {
+                mOrientation = fixedOrientation;
+                mCamFrameHandler.onUpdateCameraOrientation(mOrientation);
+            }
+        }
+
+        private void openCamera() {
+            // Acquire lock for camera
+            try {
+                if (!mCameraLock.tryLock(MAX_CAMERA_WAIT_TIME, TimeUnit.SECONDS)) {
+                    throw new RuntimeException("Timed out while waiting to acquire camera!");
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Interrupted while waiting to acquire camera!");
+            }
+
+            // Make sure external entities are not holding camera. We need to hold the lock until
+            // the preview is started again.
+            Object lockContext = new Object();
+            mExternalCameraLock.lock(lockContext);
+
+            // Need to synchronize this as many of the member values are modified during setup.
+            synchronized (this) {
+                updateCamera();
+                updateRotation();
+                mCamFrameHandler.setupServerFrame();
+            }
+
+            mCamera.startPreview();
+
+            // Inform listeners
+            synchronized (mCamListeners) {
+                for (CameraListener listener : mCamListeners) {
+                    listener.onCameraOpened(CameraStreamer.this);
+                }
+            }
+            mExternalCameraLock.unlock(lockContext);
+            // New camera started
+            mCameraReadyLock.lock();
+            mCameraReady.signal();
+            mCameraReadyLock.unlock();
+        }
+
+        /**
+         * Creates an instance of MediaRecorder to be used for the streamer.
+         * User should call the functions in the following sequence:<p>
+         *   {@link #createRecorder}<p>
+         *   {@link #startRecording}<p>
+         *   {@link #stopRecording}<p>
+         *   {@link #releaseRecorder}<p>
+         * @param outputPath the output video path for the recorder
+         * @param profile the recording {@link CamcorderProfile} which has parameters indicating
+         *  the resolution, quality etc.
+         */
+        public void createRecorder(String outputPath, CamcorderProfile profile) {
+            lockCamera(this);
+            mCamera.unlock();
+            if (mRecorder != null) {
+                mRecorder.release();
+            }
+            mRecorder = new MediaRecorder();
+            mRecorder.setCamera(mCamera);
+            mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+            mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+            mRecorder.setProfile(profile);
+            mRecorder.setOutputFile(outputPath);
+            try {
+                mRecorder.prepare();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /**
+         * Starts recording video using the created MediaRecorder object
+         */
+        public void startRecording() {
+            if (mRecorder == null) {
+                throw new RuntimeException("No recorder created");
+            }
+            mRecorder.start();
+        }
+
+        /**
+         * Stops recording video
+         */
+        public void stopRecording() {
+            if (mRecorder == null) {
+                throw new RuntimeException("No recorder created");
+            }
+            mRecorder.stop();
+        }
+
+        /**
+         * Release the resources held by the MediaRecorder, call this after done recording.
+         */
+        public void releaseRecorder() {
+            if (mRecorder == null) {
+                throw new RuntimeException("No recorder created");
+            }
+            mRecorder.release();
+            mRecorder = null;
+            mCamera.lock();
+            unlockCamera(this);
+        }
+
+        private void closeCamera() {
+            Object lockContext = new Object();
+            mExternalCameraLock.lock(lockContext);
+            if (mCamera != null) {
+                mCamera.stopPreview();
+                mCamera.release();
+                mCamera = null;
+            }
+            mCameraLock.unlock();
+            mCamFrameHandler.release();
+            mExternalCameraLock.unlock(lockContext);
+            // Inform listeners
+            synchronized (mCamListeners) {
+                for (CameraListener listener : mCamListeners) {
+                    listener.onCameraClosed(CameraStreamer.this);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * The frame-client callback interface.
+     * FrameClients, that wish to receive Frames from the camera must implement this callback
+     * method.
+     * Note, that this method is called on the Camera server thread. However, the
+     * {@code getLatestFrame()} method must be called from the client thread.
+     */
+    public static interface FrameClient {
+        public void onCameraFrameAvailable();
+    }
+
+    /**
+     * The CameraListener callback interface.
+     * This interface allows observers to monitor the CameraStreamer and respond to stream open
+     * and close events.
+     */
+    public static interface CameraListener {
+        /**
+         * Called when the camera is opened and begins producing frames.
+         * This is also called when settings have changed that caused the camera to be reopened.
+         */
+        public void onCameraOpened(CameraStreamer camera);
+
+        /**
+         * Called when the camera is closed and stops producing frames.
+         */
+        public void onCameraClosed(CameraStreamer camera);
+    }
+
+    /**
+     * Manually update the display rotation.
+     * You do not need to call this, if the camera is bound to a display, or your app does not
+     * support multiple orientations.
+     */
+    public void updateDisplayRotation(int rotation) {
+        mCameraRunner.updateDisplayRotation(rotation);
+    }
+
+    /**
+     * Bind the camera to your Activity's display.
+     * Use this, if your Activity supports multiple display orientation, and you would like the
+     * camera to update accordingly when the orientation is changed.
+     */
+    public void bindToDisplay(Display display) {
+        mCameraRunner.bindToDisplay(display);
+    }
+
+    /**
+     * Sets the desired preview size.
+     * Note that the actual width and height may vary.
+     *
+     * @param width The desired width of the preview camera stream.
+     * @param height The desired height of the preview camera stream.
+     */
+    public void setDesiredPreviewSize(int width, int height) {
+        mCameraRunner.setDesiredPreviewSize(width, height);
+    }
+
+    /**
+     * Sets the desired picture size.
+     * Note that the actual width and height may vary.
+     *
+     * @param width The desired picture width.
+     * @param height The desired picture height.
+     */
+    public void setDesiredPictureSize(int width, int height) {
+        mCameraRunner.setDesiredPictureSize(width, height);
+    }
+
+    /**
+     * Sets the desired camera frame-rate.
+     * Note, that the actual frame-rate may vary.
+     *
+     * @param fps The desired FPS.
+     */
+    public void setDesiredFrameRate(int fps) {
+        mCameraRunner.setDesiredFrameRate(fps);
+    }
+
+    /**
+     * Sets the camera facing direction.
+     *
+     * Specify {@code FACING_DONTCARE} (default) if you would like the CameraStreamer to choose
+     * the direction. When specifying any other direction be sure to first check whether the
+     * device supports the desired facing.
+     *
+     * @param facing The desired camera facing direction.
+     */
+    public void setFacing(int facing) {
+        mCameraRunner.setFacing(facing);
+    }
+
+    /**
+     * Set whether to flip the camera image horizontally when using the front facing camera.
+     */
+    public void setFlipFrontCamera(boolean flipFront) {
+        mCameraRunner.setFlipFrontCamera(flipFront);
+    }
+
+    /**
+     * Sets the camera flash mode.
+     *
+     * This must be one of the String constants defined in the Camera.Parameters class.
+     *
+     * @param flashMode A String constant specifying the flash mode.
+     */
+    public void setFlashMode(String flashMode) {
+        mCameraRunner.setFlashMode(flashMode);
+    }
+
+    /**
+     * Returns the current flash mode.
+     *
+     * This returns the currently running camera's flash-mode, or NULL if flash modes are not
+     * supported on that camera.
+     *
+     * @return The flash mode String, or NULL if flash modes are not supported.
+     */
+    public String getFlashMode() {
+        return mCameraRunner.getFlashMode();
+    }
+
+    /**
+     * Get the actual camera facing.
+     * Returns 0 if actual facing is not yet known.
+     */
+    public int getCameraFacing() {
+        return mCameraRunner.getCameraFacing();
+    }
+
+    /**
+     * Get the current camera rotation.
+     *
+     * Use this rotation if you want to snap pictures from the camera and need to rotate the
+     * picture to be up-right.
+     *
+     * @return the current camera rotation.
+     */
+    public int getCameraRotation() {
+        return mCameraRunner.getCameraRotation();
+    }
+
+    /**
+     * Specifies whether or not the camera supports hardware face detection.
+     * @return true, if the camera supports hardware face detection.
+     */
+    public boolean supportsHardwareFaceDetection() {
+        return mCameraRunner.supportsHardwareFaceDetection();
+    }
+
+    /**
+     * Returns the camera facing that is chosen when DONT_CARE is specified.
+     * Returns 0 if neither a front nor back camera could be found.
+     */
+    public static int getDefaultFacing() {
+        int camCount = Camera.getNumberOfCameras();
+        if (camCount == 0) {
+            return 0;
+        } else {
+            CameraInfo cameraInfo = new CameraInfo();
+            Camera.getCameraInfo(0, cameraInfo);
+            return (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT)
+                ? FACING_FRONT
+                : FACING_BACK;
+        }
+    }
+
+    /**
+     * Get the actual camera width.
+     * Returns 0 if actual width is not yet known.
+     */
+    public int getCameraWidth() {
+        return mCameraRunner.getCameraWidth();
+    }
+
+    /**
+     * Get the actual camera height.
+     * Returns 0 if actual height is not yet known.
+     */
+    public int getCameraHeight() {
+        return mCameraRunner.getCameraHeight();
+    }
+
+    /**
+     * Get the actual camera frame-rate.
+     * Returns 0 if actual frame-rate is not yet known.
+     */
+    public int getCameraFrameRate() {
+        return mCameraRunner.getCameraFrameRate();
+    }
+
+    /**
+     * Returns true if the camera can be started at this point.
+     */
+    public boolean canStart() {
+        return mCameraRunner.canStart();
+    }
+
+    /**
+     * Returns true if the camera is currently running.
+     */
+    public boolean isRunning() {
+        return mCameraRunner.isRunning();
+    }
+
+    /**
+     * Starts the camera.
+     */
+    public void start() {
+        mCameraRunner.pushEvent(Event.START, true);
+    }
+
+    /**
+     * Stops the camera.
+     */
+    public void stop() {
+        mCameraRunner.pushEvent(Event.STOP, true);
+    }
+
+    /**
+     * Stops the camera and waits until it is completely closed. Generally, this should not be
+     * called in the UI thread, but may be necessary if you need the camera to be closed before
+     * performing subsequent steps.
+     */
+    public void stopAndWait() {
+        mCameraRunner.pushEvent(Event.STOP, true);
+        try {
+            if (!mCameraLock.tryLock(MAX_CAMERA_WAIT_TIME, TimeUnit.SECONDS)) {
+                Log.w("CameraStreamer", "Time-out waiting for camera to close!");
+            }
+        } catch (InterruptedException e) {
+            Log.w("CameraStreamer", "Interrupted while waiting for camera to close!");
+        }
+        mCameraLock.unlock();
+    }
+
+    /**
+     * Registers a listener to handle camera state changes.
+     */
+    public void addListener(CameraListener listener) {
+        mCameraRunner.addListener(listener);
+    }
+
+    /**
+     * Unregisters a listener to handle camera state changes.
+     */
+    public void removeListener(CameraListener listener) {
+        mCameraRunner.removeListener(listener);
+    }
+
+    /**
+     * Registers the frame-client with the camera.
+     * This MUST be called from the client thread!
+     */
+    public void registerClient(FrameClient client) {
+        mCameraRunner.getCamFrameHandler().registerClient(client);
+    }
+
+    /**
+     * Unregisters the frame-client with the camera.
+     * This MUST be called from the client thread!
+     */
+    public void unregisterClient(FrameClient client) {
+        mCameraRunner.getCamFrameHandler().unregisterClient(client);
+    }
+
+    /**
+     * Gets the latest camera frame for the client.
+     *
+     * This must be called from the same thread as the {@link #registerClient(FrameClient)} call!
+     * The frame passed in will be resized by the camera streamer to fit the camera frame.
+     * Returns false if the frame could not be grabbed. This may happen if the camera has been
+     * closed in the meantime, and its resources let go.
+     *
+     * @return true, if the frame was grabbed successfully.
+     */
+    public boolean getLatestFrame(FrameImage2D targetFrame) {
+        return mCameraRunner.grabFrame(targetFrame);
+    }
+
+    /**
+     * Expose the underlying android.hardware.Camera object.
+     * Use the returned object with care: some camera functions may break the functionality
+     * of CameraStreamer.
+     * @return the Camera object.
+     */
+    @Deprecated
+    public Camera getCamera() {
+        return mCameraRunner.getCamera();
+    }
+
+    /**
+     * Obtain access to the underlying android.hardware.Camera object.
+     * This grants temporary access to the internal Camera handle. Once you are done using the
+     * handle you must call {@link #unlockCamera(Object)}. While you are holding the Camera,
+     * it will not be modified or released by the CameraStreamer. The Camera object return is
+     * guaranteed to have the preview running.
+     *
+     * The CameraStreamer does not account for changes you make to the Camera. That is, if you
+     * change the Camera unexpectedly this may cause unintended behavior by the streamer.
+     *
+     * Note that the returned object may be null. This can happen when the CameraStreamer is not
+     * running, or is just transitioning to another Camera, such as during a switch from front to
+     * back Camera.
+     * @param context an object used as a context for locking and unlocking. lockCamera and
+     *   unlockCamera should use the same context object.
+     * @return The Camera object.
+     */
+    public Camera lockCamera(Object context) {
+        return mCameraRunner.lockCamera(context);
+    }
+
+    /**
+     * Release the acquire Camera object.
+     * @param context the context object that used when lockCamera is called.
+     */
+    public void unlockCamera(Object context) {
+        mCameraRunner.unlockCamera(context);
+    }
+
+    /**
+     * Creates an instance of MediaRecorder to be used for the streamer.
+     * User should call the functions in the following sequence:<p>
+     *   {@link #createRecorder}<p>
+     *   {@link #startRecording}<p>
+     *   {@link #stopRecording}<p>
+     *   {@link #releaseRecorder}<p>
+     * @param path the output video path for the recorder
+     * @param profile the recording {@link CamcorderProfile} which has parameters indicating
+     *  the resolution, quality etc.
+     */
+    public void createRecorder(String path, CamcorderProfile profile) {
+        mCameraRunner.createRecorder(path, profile);
+    }
+
+    public void releaseRecorder() {
+        mCameraRunner.releaseRecorder();
+    }
+
+    public void startRecording() {
+        mCameraRunner.startRecording();
+    }
+
+    public void stopRecording() {
+        mCameraRunner.stopRecording();
+    }
+
+    /**
+     * Retrieve the ID of the currently used camera.
+     * @return the ID of the currently used camera.
+     */
+    public int getCameraId() {
+        return mCameraRunner.getCurrentCameraId();
+    }
+
+    /**
+     * @return The number of cameras available for streaming on this device.
+     */
+    public static int getNumberOfCameras() {
+        // Currently, this is just the number of cameras that are available on the device.
+        return Camera.getNumberOfCameras();
+    }
+
+    CameraStreamer(MffContext context) {
+        mCameraRunner = new CameraRunnable(context);
+    }
+
+    /** Halt is like stop, but may be resumed using restart(). */
+    void halt() {
+        mCameraRunner.pushEvent(Event.HALT, true);
+    }
+
+    /** Restart starts the camera only if previously halted. */
+    void restart() {
+        mCameraRunner.pushEvent(Event.RESTART, true);
+    }
+
+    static boolean requireDummySurfaceView() {
+        return VERSION.SDK_INT < 15;
+    }
+
+    void tearDown() {
+        mCameraRunner.pushEvent(Event.TEARDOWN, true);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorSpace.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorSpace.java
new file mode 100644
index 0000000..f2bfe08
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorSpace.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility functions to convert between color-spaces.
+ *
+ * Currently these methods are all CPU based native methods. These could be updated in the future
+ * to provide other implementations.
+ */
+public class ColorSpace {
+
+    /**
+     * Convert YUV420-Planer data to RGBA8888.
+     *
+     * The input data is expected to be laid out in 3 planes. The width x height Y plane, followed
+     * by the U and V planes, where each chroma value corresponds to a 2x2 luminance value block.
+     * YUV to RGB conversion is done using the ITU-R BT.601 transformation. The output buffer must
+     * be large enough to hold the data, and the dimensions must be multiples of 2.
+     *
+     * @param input data encoded in YUV420-Planar.
+     * @param output buffer to hold RGBA8888 data.
+     * @param width the width of the image (must be a multiple of 2)
+     * @param height the height of the image (must be a multiple of 2)
+     */
+    public static void convertYuv420pToRgba8888(
+            ByteBuffer input, ByteBuffer output, int width, int height) {
+        expectInputSize(input, (3 * width * height) / 2);
+        expectOutputSize(output, width * height * 4);
+        nativeYuv420pToRgba8888(input, output, width, height);
+    }
+
+    /**
+     * Convert ARGB8888 to RGBA8888.
+     *
+     * The input data is expected to be encoded in 8-bit interleaved ARGB channels. The output
+     * buffer must be large enough to hold the data. The output buffer may be the same as the
+     * input buffer.
+     *
+     * @param input data encoded in ARGB8888.
+     * @param output buffer to hold RGBA8888 data.
+     * @param width the width of the image
+     * @param height the height of the image
+     */
+    public static void convertArgb8888ToRgba8888(
+            ByteBuffer input, ByteBuffer output, int width, int height) {
+        expectInputSize(input, width * height * 4);
+        expectOutputSize(output, width * height * 4);
+        nativeArgb8888ToRgba8888(input, output, width, height);
+    }
+
+    /**
+     * Convert RGBA8888 to HSVA8888.
+     *
+     * The input data is expected to be encoded in 8-bit interleaved RGBA channels. The output
+     * buffer must be large enough to hold the data. The output buffer may be the same as the
+     * input buffer.
+     *
+     * @param input data encoded in RGBA8888.
+     * @param output buffer to hold HSVA8888 data.
+     * @param width the width of the image
+     * @param height the height of the image
+     */
+    public static void convertRgba8888ToHsva8888(
+            ByteBuffer input, ByteBuffer output, int width, int height) {
+        expectInputSize(input, width * height * 4);
+        expectOutputSize(output, width * height * 4);
+        nativeRgba8888ToHsva8888(input, output, width, height);
+    }
+
+    /**
+     * Convert RGBA8888 to YCbCrA8888.
+     *
+     * The input data is expected to be encoded in 8-bit interleaved RGBA channels. The output
+     * buffer must be large enough to hold the data. The output buffer may be the same as the
+     * input buffer.
+     *
+     * @param input data encoded in RGBA8888.
+     * @param output buffer to hold YCbCrA8888 data.
+     * @param width the width of the image
+     * @param height the height of the image
+     */
+    public static void convertRgba8888ToYcbcra8888(
+            ByteBuffer input, ByteBuffer output, int width, int height) {
+        expectInputSize(input, width * height * 4);
+        expectOutputSize(output, width * height * 4);
+        nativeRgba8888ToYcbcra8888(input, output, width, height);
+    }
+
+    private static void expectInputSize(ByteBuffer input, int expectedSize) {
+        if (input.remaining() < expectedSize) {
+            throw new IllegalArgumentException("Input buffer's size does not fit given width "
+                    + "and height! Expected: " + expectedSize + ", Got: " + input.remaining()
+                    + ".");
+        }
+    }
+
+    private static void expectOutputSize(ByteBuffer output, int expectedSize) {
+        if (output.remaining() < expectedSize) {
+            throw new IllegalArgumentException("Output buffer's size does not fit given width "
+                    + "and height! Expected: " + expectedSize + ", Got: " + output.remaining()
+                    + ".");
+        }
+    }
+
+    private static native void nativeYuv420pToRgba8888(
+            ByteBuffer input, ByteBuffer output, int width, int height);
+
+    private static native void nativeArgb8888ToRgba8888(
+            ByteBuffer input, ByteBuffer output, int width, int height);
+
+    private static native void nativeRgba8888ToHsva8888(
+            ByteBuffer input, ByteBuffer output, int width, int height);
+
+    private static native void nativeRgba8888ToYcbcra8888(
+            ByteBuffer input, ByteBuffer output, int width, int height);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorfulnessFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorfulnessFilter.java
new file mode 100644
index 0000000..5bdf4af
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ColorfulnessFilter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+// Extract histogram from image.
+
+package androidx.media.filterpacks.colorspace;
+
+import androidx.media.filterfw.FrameValue;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * ColorfulnessFilter takes in a particular Chroma histogram generated by NewChromaHistogramFilter
+ * and compute the colorfulness based on the entropy in Hue space.
+ */
+public final class ColorfulnessFilter extends Filter {
+
+    public ColorfulnessFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType dataIn = FrameType.buffer2D(FrameType.ELEMENT_FLOAT32);
+        return new Signature()
+            .addInputPort("histogram", Signature.PORT_REQUIRED, dataIn)
+            .addOutputPort("score", Signature.PORT_REQUIRED, FrameType.single(float.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameBuffer2D histogramFrame =
+                getConnectedInputPort("histogram").pullFrame().asFrameBuffer2D();
+        ByteBuffer byteBuffer = histogramFrame.lockBytes(Frame.MODE_READ);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        FloatBuffer histogramBuffer = byteBuffer.asFloatBuffer();
+        histogramBuffer.rewind();
+
+        // Create a hue histogram from hue-saturation histogram
+        int hueBins = histogramFrame.getWidth();
+        int saturationBins = histogramFrame.getHeight() - 1;
+        float[] hueHistogram = new float[hueBins];
+        float total = 0;
+        for (int r = 0; r < saturationBins; ++r) {
+            float weight = (float) Math.pow(2, r);
+            for (int c = 0; c < hueBins; c++) {
+                float value = histogramBuffer.get() * weight;
+                hueHistogram[c] += value;
+                total += value;
+            }
+        }
+        float colorful = 0f;
+        for (int c = 0; c < hueBins; ++c) {
+            float value = hueHistogram[c] / total;
+            if (value > 0f) {
+                colorful -= value * ((float) Math.log(value));
+            }
+        }
+
+        colorful /= Math.log(2);
+
+        histogramFrame.unlock();
+        OutputPort outPort = getConnectedOutputPort("score");
+        FrameValue frameValue = outPort.fetchAvailableFrame(null).asFrameValue();
+        frameValue.setValue(colorful);
+        outPort.pushFrame(frameValue);
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CropFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CropFilter.java
new file mode 100644
index 0000000..91fe21c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/CropFilter.java
@@ -0,0 +1,165 @@
+/*
+ * 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 androidx.media.filterpacks.transform;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.util.FloatMath;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.geometry.Quad;
+
+public class CropFilter extends Filter {
+
+    private Quad mCropRect = Quad.fromRect(0f, 0f, 1f, 1f);
+    private int mOutputWidth = 0;
+    private int mOutputHeight = 0;
+    private ImageShader mShader;
+    private boolean mUseMipmaps = false;
+    private FrameImage2D mPow2Frame = null;
+
+    public CropFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("cropRect", Signature.PORT_REQUIRED, FrameType.single(Quad.class))
+            .addInputPort("outputWidth", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("outputHeight", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("useMipmaps", Signature.PORT_OPTIONAL, FrameType.single(boolean.class))
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("cropRect")) {
+            port.bindToFieldNamed("mCropRect");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("outputWidth")) {
+            port.bindToFieldNamed("mOutputWidth");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("outputHeight")) {
+            port.bindToFieldNamed("mOutputHeight");
+            port.setAutoPullEnabled(true);
+        } else  if (port.getName().equals("useMipmaps")) {
+            port.bindToFieldNamed("mUseMipmaps");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onPrepare() {
+        if (isOpenGLSupported()) {
+            mShader = ImageShader.createIdentity();
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("image");
+
+        // Pull input frame
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        int[] inDims = inputImage.getDimensions();
+        int[] croppedDims = { (int)FloatMath.ceil(mCropRect.xEdge().length() * inDims[0]),
+                              (int)FloatMath.ceil(mCropRect.yEdge().length() * inDims[1]) };
+        int[] outDims = { getOutputWidth(croppedDims[0], croppedDims[1]),
+                getOutputHeight(croppedDims[0], croppedDims[1]) };
+        FrameImage2D outputImage = outPort.fetchAvailableFrame(outDims).asFrameImage2D();
+
+        if (isOpenGLSupported()) {
+            FrameImage2D sourceFrame;
+            Quad sourceQuad = null;
+            boolean scaleDown = (outDims[0] < croppedDims[0]) || (outDims[1] < croppedDims[1]);
+            if (scaleDown && mUseMipmaps) {
+                mPow2Frame = TransformUtils.makeMipMappedFrame(mPow2Frame, croppedDims);
+                int[] extDims = mPow2Frame.getDimensions();
+                float targetWidth = croppedDims[0] / (float)extDims[0];
+                float targetHeight = croppedDims[1] / (float)extDims[1];
+                Quad targetQuad = Quad.fromRect(0f, 0f, targetWidth, targetHeight);
+                mShader.setSourceQuad(mCropRect);
+                mShader.setTargetQuad(targetQuad);
+                mShader.process(inputImage, mPow2Frame);
+                TransformUtils.generateMipMaps(mPow2Frame);
+                sourceFrame = mPow2Frame;
+                sourceQuad = targetQuad;
+            } else {
+                sourceFrame = inputImage;
+                sourceQuad = mCropRect;
+            }
+
+            mShader.setSourceQuad(sourceQuad);
+            mShader.setTargetRect(0f, 0f, 1f, 1f);
+            mShader.process(sourceFrame, outputImage);
+        } else {
+            // Convert quads to canvas coordinate space
+            Quad sourceQuad = mCropRect.scale2(inDims[0], inDims[1]);
+            Quad targetQuad = Quad.fromRect(0f, 0f, inDims[0], inDims[1]);
+
+            // Calculate transform for crop
+            Matrix transform = Quad.getTransform(sourceQuad, targetQuad);
+            transform.postScale(outDims[0] / (float)inDims[0], outDims[1] / (float)inDims[1]);
+
+            // Create target canvas
+            Bitmap.Config config = Bitmap.Config.ARGB_8888;
+            Bitmap cropped = Bitmap.createBitmap(outDims[0], outDims[1], config);
+            Canvas canvas = new Canvas(cropped);
+
+            // Draw source bitmap into target canvas
+            Paint paint = new Paint();
+            paint.setFilterBitmap(true);
+            Bitmap sourceBitmap = inputImage.toBitmap();
+            canvas.drawBitmap(sourceBitmap, transform, paint);
+
+            // Assign bitmap to output frame
+            outputImage.setBitmap(cropped);
+        }
+
+        outPort.pushFrame(outputImage);
+    }
+
+    @Override
+    protected void onClose() {
+        if (mPow2Frame != null){
+            mPow2Frame.release();
+            mPow2Frame = null;
+        }
+    }
+
+    protected int getOutputWidth(int inWidth, int inHeight) {
+        return mOutputWidth <= 0 ? inWidth : mOutputWidth;
+    }
+
+    protected int getOutputHeight(int inWidth, int inHeight) {
+        return mOutputHeight <= 0 ? inHeight : mOutputHeight;
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Filter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Filter.java
new file mode 100644
index 0000000..9e2eb92
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Filter.java
@@ -0,0 +1,766 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Filters are the processing nodes of the filter graphs.
+ *
+ * Filters may have any number of input and output ports, through which the data frames flow.
+ * TODO: More documentation on filter life-cycle, port and type checking, GL and RenderScript, ...
+ */
+public abstract class Filter {
+
+    private static class State {
+        private static final int STATE_UNPREPARED = 1;
+        private static final int STATE_PREPARED = 2;
+        private static final int STATE_OPEN = 3;
+        private static final int STATE_CLOSED = 4;
+        private static final int STATE_DESTROYED = 5;
+
+        public int current = STATE_UNPREPARED;
+
+        public synchronized boolean check(int state) {
+            return current == state;
+        }
+
+    }
+
+    private final int REQUEST_FLAG_NONE = 0;
+    private final int REQUEST_FLAG_CLOSE = 1;
+
+    private String mName;
+    private MffContext mContext;
+    private FilterGraph mFilterGraph;
+
+    private State mState = new State();
+    private int mRequests = REQUEST_FLAG_NONE;
+
+    private int mMinimumAvailableInputs = 1;
+    private int mMinimumAvailableOutputs = 1;
+
+    private int mScheduleCount = 0;
+    private long mLastScheduleTime = 0;
+
+    private boolean mIsActive = true;
+    private AtomicBoolean mIsSleeping = new AtomicBoolean(false);
+
+    private long mCurrentTimestamp = Frame.TIMESTAMP_NOT_SET;
+
+    private HashMap<String, InputPort> mConnectedInputPorts = new HashMap<String, InputPort>();
+    private HashMap<String, OutputPort> mConnectedOutputPorts = new HashMap<String, OutputPort>();
+
+    private InputPort[] mConnectedInputPortArray = null;
+    private OutputPort[] mConnectedOutputPortArray = null;
+
+    private ArrayList<Frame> mAutoReleaseFrames = new ArrayList<Frame>();
+
+
+    /**
+     * Constructs a new filter.
+     * A filter is bound to a specific MffContext. Its name can be any String value, but it must
+     * be unique within the filter graph.
+     *
+     * Note that names starting with "$" are reserved for internal use, and should not be used.
+     *
+     * @param context The MffContext in which the filter will live.
+     * @param name The name of the filter.
+     */
+    protected Filter(MffContext context, String name) {
+        mName = name;
+        mContext = context;
+    }
+
+    /**
+     * Checks whether the filter class is available on this platform.
+     * Some filters may not be installed on all platforms and can therefore not be instantiated.
+     * Before instantiating a filter, check if it is available by using this method.
+     *
+     * This method uses the shared FilterFactory to check whether the filter class is available.
+     *
+     * @param filterClassName The fully qualified class name of the Filter class.
+     * @return true, if filters of the specified class name are available.
+     */
+    public static final boolean isAvailable(String filterClassName) {
+        return FilterFactory.sharedFactory().isFilterAvailable(filterClassName);
+    }
+
+    /**
+     * Returns the name of this filter.
+     *
+     * @return the name of the filter (specified during construction).
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the signature of this filter.
+     *
+     * Subclasses should override this and return their filter signature. The default
+     * implementation returns a generic signature with no constraints.
+     *
+     * This method may be called at any time.
+     *
+     * @return the Signature instance for this filter.
+     */
+    public Signature getSignature() {
+        return new Signature();
+    }
+
+    /**
+     * Returns the MffContext that the filter resides in.
+     *
+     * @return the MffContext of the filter.
+     */
+    public MffContext getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns true, if the filter is active.
+     * TODO: thread safety?
+     *
+     * @return true, if the filter is active.
+     */
+    public boolean isActive() {
+        return mIsActive;
+    }
+
+    /**
+     * Activates the current filter.
+     * Only active filters can be scheduled for execution. This method can only be called if the
+     * GraphRunner that is executing the filter is stopped or paused.
+     */
+    public void activate() {
+        assertIsPaused();
+        if (!mIsActive) {
+            mIsActive = true;
+        }
+    }
+
+    /**
+     * Deactivates the current filter.
+     * Only active filters can be scheduled for execution. This method can only be called if the
+     * GraphRunner that is executing the filter is stopped or paused.
+     */
+    public void deactivate() {
+        // TODO: Support close-on-deactivate (must happen in processing thread).
+        assertIsPaused();
+        if (mIsActive) {
+            mIsActive = false;
+        }
+    }
+
+    /**
+     * Returns the filter's set of input ports.
+     * Note that this contains only the *connected* input ports. To retrieve all
+     * input ports that this filter accepts, one has to go via the filter's Signature.
+     *
+     * @return An array containing all connected input ports.
+     */
+    public final InputPort[] getConnectedInputPorts() {
+        return mConnectedInputPortArray;
+    }
+
+    /**
+     * Returns the filter's set of output ports.
+     * Note that this contains only the *connected* output ports. To retrieve all
+     * output ports that this filter provides, one has to go via the filter's Signature.
+     *
+     * @return An array containing all connected output ports.
+     */
+    public final OutputPort[] getConnectedOutputPorts() {
+        return mConnectedOutputPortArray;
+    }
+
+    /**
+     * Returns the input port with the given name.
+     * Note that this can only access the *connected* input ports. To retrieve all
+     * input ports that this filter accepts, one has to go via the filter's Signature.
+     *
+     * @return the input port with the specified name, or null if no connected input port
+     *  with this name exists.
+     */
+    public final InputPort getConnectedInputPort(String name) {
+        return mConnectedInputPorts.get(name);
+    }
+
+    /**
+     * Returns the output port with the given name.
+     * Note that this can only access the *connected* output ports. To retrieve all
+     * output ports that this filter provides, one has to go via the filter's Signature.
+     *
+     * @return the output port with the specified name, or null if no connected output port
+     *  with this name exists.
+     */
+    public final OutputPort getConnectedOutputPort(String name) {
+        return mConnectedOutputPorts.get(name);
+    }
+
+    /**
+     * Called when an input port has been attached in the graph.
+     * Override this method, in case you want to be informed of any connected input ports, or make
+     * modifications to them. Note that you may not assume that any other ports have been attached
+     * already. If you have dependencies on other ports, override
+     * {@link #onInputPortOpen(InputPort)}. The default implementation does nothing.
+     *
+     * @param port The InputPort instance that was attached.
+     */
+    protected void onInputPortAttached(InputPort port) {
+    }
+
+    /**
+     * Called when an output port has been attached in the graph.
+     * Override this method, in case you want to be informed of any connected output ports, or make
+     * modifications to them. Note that you may not assume that any other ports have been attached
+     * already. If you have dependencies on other ports, override
+     * {@link #onOutputPortOpen(OutputPort)}. The default implementation does nothing.
+     *
+     * @param port The OutputPort instance that was attached.
+     */
+    protected void onOutputPortAttached(OutputPort port) {
+    }
+
+    /**
+     * Called when an input port is opened on this filter.
+     * Input ports are opened by the data produce, that is the filter that is connected to an
+     * input port. Override this if you need to make modifications to the port before processing
+     * begins. Note, that this is only called if the connected filter is scheduled. You may assume
+     * that all ports are attached when this is called.
+     *
+     * @param port The InputPort instance that was opened.
+     */
+    protected void onInputPortOpen(InputPort port) {
+    }
+
+    /**
+     * Called when an output port is opened on this filter.
+     * Output ports are opened when the filter they are attached to is opened. Override this if you
+     * need to make modifications to the port before processing begins. Note, that this is only
+     * called if the filter is scheduled. You may assume that all ports are attached when this is
+     * called.
+     *
+     * @param port The OutputPort instance that was opened.
+     */
+    protected void onOutputPortOpen(OutputPort port) {
+    }
+
+    /**
+     * Returns true, if the filter is currently open.
+     * @return true, if the filter is currently open.
+     */
+    public final boolean isOpen() {
+        return mState.check(State.STATE_OPEN);
+    }
+
+    @Override
+    public String toString() {
+        return mName + " (" + getClass().getSimpleName() + ")";
+    }
+
+    /**
+     * Called when filter is prepared.
+     * Subclasses can override this to prepare the filter for processing. This method gets called
+     * once only just before the filter is scheduled for processing the first time.
+     *
+     * @see #onTearDown()
+     */
+    protected void onPrepare() {
+    }
+
+    /**
+     * Called when the filter is opened.
+     * Subclasses can override this to perform any kind of initialization just before processing
+     * starts. This method may be called any number of times, but is always balanced with an
+     * {@link #onClose()} call.
+     *
+     * @see #onClose()
+     */
+    protected void onOpen() {
+    }
+
+    /**
+     * Called to perform processing on Frame data.
+     * This is the only method subclasses must override. It is called every time the filter is
+     * ready for processing. Typically this is when there is input data to process and available
+     * output ports, but may differ depending on the port configuration.
+     */
+    protected abstract void onProcess();
+
+    /**
+     * Called when the filter is closed.
+     * Subclasses can override this to perform any kind of post-processing steps. Processing will
+     * not resume until {@link #onOpen()} is called again. This method is only called if the filter
+     * is open.
+     *
+     * @see #onOpen()
+     */
+    protected void onClose() {
+    }
+
+    /**
+     * Called when the filter is torn down.
+     * Subclasses can override this to perform clean-up tasks just before the filter is disposed of.
+     * It is called when the filter graph that the filter belongs to is disposed.
+     *
+     * @see #onPrepare()
+     */
+    protected void onTearDown() {
+    }
+
+    /**
+     * Check if the input conditions are met in order to schedule this filter.
+     *
+     * This is used by {@link #canSchedule()} to determine if the input-port conditions given by
+     * the filter are met. Subclasses that override scheduling behavior can make use of this
+     * function.
+     *
+     * @return true, if the filter's input conditions are met.
+     */
+    protected boolean inputConditionsMet() {
+        if (mConnectedInputPortArray.length > 0) {
+            int inputFrames = 0;
+            // [Non-iterator looping]
+            for (int i = 0; i < mConnectedInputPortArray.length; ++i) {
+                if (!mConnectedInputPortArray[i].conditionsMet()) {
+                    return false;
+                } else if (mConnectedInputPortArray[i].hasFrame()) {
+                    ++inputFrames;
+                }
+            }
+            if (inputFrames < mMinimumAvailableInputs) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check if the output conditions are met in order to schedule this filter.
+     *
+     * This is used by {@link #canSchedule()} to determine if the output-port conditions given by
+     * the filter are met. Subclasses that override scheduling behavior can make use of this
+     * function.
+     *
+     * @return true, if the filter's output conditions are met.
+     */
+    protected boolean outputConditionsMet() {
+        if (mConnectedOutputPortArray.length > 0) {
+            int availableOutputs = 0;
+            for (int i = 0; i < mConnectedOutputPortArray.length; ++i) {
+                if (!mConnectedOutputPortArray[i].conditionsMet()) {
+                    return false;
+                } else if (mConnectedOutputPortArray[i].isAvailable()) {
+                    ++availableOutputs;
+                }
+            }
+            if (availableOutputs < mMinimumAvailableOutputs) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check if the Filter is in a state so that it can be scheduled.
+     *
+     * When overriding the filter's {@link #canSchedule()} method, you should never allow
+     * scheduling a filter that is not in a schedulable state. This will result in undefined
+     * behavior.
+     *
+     * @return true, if the filter is in a schedulable state.
+     */
+    protected boolean inSchedulableState() {
+        return (mIsActive && !mState.check(State.STATE_CLOSED));
+    }
+
+    /**
+     * Returns true if the filter can be currently scheduled.
+     *
+     * Filters may override this method if they depend on custom factors that determine whether
+     * they can be scheduled or not. The scheduler calls this method to determine whether or not
+     * a filter can be scheduled for execution. It does not guarantee that it will be executed.
+     * It is strongly recommended to call super's implementation to make sure your filter can be
+     * scheduled based on its state, input and output ports.
+     *
+     * @return true, if the filter can be scheduled.
+     */
+    protected boolean canSchedule() {
+        return inSchedulableState() && inputConditionsMet() && outputConditionsMet();
+    }
+
+    /**
+     * Returns the current FrameManager instance.
+     * @return the current FrameManager instance or null if there is no FrameManager set up yet.
+     */
+    protected final FrameManager getFrameManager() {
+        return mFilterGraph.mRunner != null ? mFilterGraph.mRunner.getFrameManager() : null;
+    }
+
+    /**
+     * Returns whether the GraphRunner for this filter is running.
+     *
+     * Generally, this method should not be used for performing operations that need to be carried
+     * out before running begins. Use {@link #performPreparation(Runnable)} for this.
+     *
+     * @return true, if the GraphRunner for this filter is running.
+     */
+    protected final boolean isRunning() {
+        return mFilterGraph != null && mFilterGraph.mRunner != null
+                && mFilterGraph.mRunner.isRunning();
+    }
+
+    /**
+     * Performs operations before the filter is running.
+     *
+     * Use this method when your filter requires to perform operations while the graph is not
+     * running. The filter will not be scheduled for execution until your method has completed
+     * execution.
+     */
+    protected final boolean performPreparation(Runnable runnable) {
+        synchronized (mState) {
+            if (mState.current == State.STATE_OPEN) {
+                return false;
+            } else {
+                runnable.run();
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Request that this filter be closed after the current processing step.
+     *
+     * Implementations may call this within their {@link #onProcess()} calls to indicate that the
+     * filter is done processing and wishes to be closed. After such a request the filter will be
+     * closed and no longer receive {@link #onProcess()} calls.
+     *
+     * @see #onClose()
+     * @see #onProcess()
+     */
+    protected final void requestClose() {
+        mRequests |= REQUEST_FLAG_CLOSE;
+    }
+
+    /**
+     * Sets the minimum number of input frames required to process.
+     * A filter will not be scheduled unless at least a certain number of input frames are available
+     * on the input ports. This is only relevant if the filter has input ports and is not waiting on
+     * all ports.
+     * The default value is 1.
+     *
+     * @param count the minimum number of frames required to process.
+     * @see #getMinimumAvailableInputs()
+     * @see #setMinimumAvailableOutputs(int)
+     * @see InputPort#setWaitsForFrame(boolean)
+     */
+    protected final void setMinimumAvailableInputs(int count) {
+        mMinimumAvailableInputs = count;
+    }
+
+    /**
+     * Returns the minimum number of input frames required to process this filter.
+     * The default value is 1.
+     *
+     * @return the minimum number of input frames required to process.
+     * @see #setMinimumAvailableInputs(int)
+     */
+    protected final int getMinimumAvailableInputs() {
+        return mMinimumAvailableInputs;
+    }
+
+    /**
+     * Sets the minimum number of available output ports required to process.
+     * A filter will not be scheduled unless atleast a certain number of output ports are available.
+     * This is only relevant if the filter has output ports and is not waiting on all ports. The
+     * default value is 1.
+     *
+     * @param count the minimum number of frames required to process.
+     * @see #getMinimumAvailableOutputs()
+     * @see #setMinimumAvailableInputs(int)
+     * @see OutputPort#setWaitsUntilAvailable(boolean)
+     */
+    protected final void setMinimumAvailableOutputs(int count) {
+        mMinimumAvailableOutputs = count;
+    }
+
+    /**
+     * Returns the minimum number of available outputs required to process this filter.
+     * The default value is 1.
+     *
+     * @return the minimum number of available outputs required to process.
+     * @see #setMinimumAvailableOutputs(int)
+     */
+    protected final int getMinimumAvailableOutputs() {
+        return mMinimumAvailableOutputs;
+    }
+
+    /**
+     * Puts the filter to sleep so that it is no longer scheduled.
+     * To resume scheduling the filter another thread must call wakeUp() on this filter.
+     */
+    protected final void enterSleepState() {
+        mIsSleeping.set(true);
+    }
+
+    /**
+     * Wakes the filter and resumes scheduling.
+     * This is generally called from another thread to signal that this filter should resume
+     * processing. Does nothing if filter is not sleeping.
+     */
+    protected final void wakeUp() {
+        if (mIsSleeping.getAndSet(false)) {
+            if (isRunning()) {
+                mFilterGraph.mRunner.signalWakeUp();
+            }
+        }
+    }
+
+    /**
+     * Returns whether this Filter is allowed to use OpenGL.
+     *
+     * Filters may use OpenGL if the MffContext supports OpenGL and its GraphRunner allows it.
+     *
+     * @return true, if this Filter is allowed to use OpenGL.
+     */
+   protected final boolean isOpenGLSupported() {
+        return mFilterGraph.mRunner.isOpenGLSupported();
+    }
+
+    /**
+     * Connect an output port to an input port of another filter.
+     * Connects the output port with the specified name to the input port with the specified name
+     * of the specified filter. If the input or output ports do not exist already, they are
+     * automatically created and added to the respective filter.
+     */
+    final void connect(String outputName, Filter targetFilter, String inputName) {
+        // Make sure not connected already
+        if (getConnectedOutputPort(outputName) != null) {
+            throw new RuntimeException("Attempting to connect already connected output port '"
+                + outputName + "' of filter " + this + "'!");
+        } else if (targetFilter.getConnectedInputPort(inputName) != null) {
+            throw new RuntimeException("Attempting to connect already connected input port '"
+                + inputName + "' of filter " + targetFilter + "'!");
+        }
+
+        // Establish connection
+        InputPort inputPort = targetFilter.newInputPort(inputName);
+        OutputPort outputPort = newOutputPort(outputName);
+        outputPort.setTarget(inputPort);
+
+        // Fire attachment callbacks
+        targetFilter.onInputPortAttached(inputPort);
+        onOutputPortAttached(outputPort);
+
+        // Update array of ports (which is maintained for more efficient access)
+        updatePortArrays();
+    }
+
+    final Map<String, InputPort> getConnectedInputPortMap() {
+        return mConnectedInputPorts;
+    }
+
+    final Map<String, OutputPort> getConnectedOutputPortMap() {
+        return mConnectedOutputPorts;
+    }
+
+    final void execute() {
+        synchronized (mState) {
+            autoPullInputs();
+            mLastScheduleTime = SystemClock.elapsedRealtime();
+            if (mState.current == State.STATE_UNPREPARED) {
+                onPrepare();
+                mState.current = State.STATE_PREPARED;
+            }
+            if (mState.current == State.STATE_PREPARED) {
+                openPorts();
+                onOpen();
+                mState.current = State.STATE_OPEN;
+            }
+            if (mState.current == State.STATE_OPEN) {
+                onProcess();
+                if (mRequests != REQUEST_FLAG_NONE) {
+                    processRequests();
+                }
+            }
+        }
+        autoReleaseFrames();
+        ++mScheduleCount;
+    }
+
+    final void performClose() {
+        synchronized (mState) {
+            if (mState.current == State.STATE_OPEN) {
+                onClose();
+                mIsSleeping.set(false);
+                mState.current = State.STATE_CLOSED;
+                mCurrentTimestamp = Frame.TIMESTAMP_NOT_SET;
+            }
+        }
+    }
+
+    final void softReset() {
+        synchronized (mState) {
+            performClose();
+            if (mState.current == State.STATE_CLOSED) {
+                mState.current = State.STATE_PREPARED;
+            }
+        }
+    }
+
+    final void performTearDown() {
+        synchronized (mState) {
+            if (mState.current == State.STATE_OPEN) {
+                throw new RuntimeException("Attempting to tear-down filter " + this + " which is "
+                    + "in an open state!");
+            } else if (mState.current != State.STATE_DESTROYED
+                    && mState.current != State.STATE_UNPREPARED) {
+                onTearDown();
+                mState.current = State.STATE_DESTROYED;
+            }
+        }
+    }
+
+    final void insertIntoFilterGraph(FilterGraph graph) {
+        mFilterGraph = graph;
+        updatePortArrays();
+    }
+
+    final int getScheduleCount() {
+        return mScheduleCount;
+    }
+
+    final void resetScheduleCount() {
+        mScheduleCount = 0;
+    }
+
+    final void openPorts() {
+        // Opening the output ports will open the connected input ports
+        for (OutputPort outputPort : mConnectedOutputPorts.values()) {
+            openOutputPort(outputPort);
+        }
+    }
+
+    final void addAutoReleaseFrame(Frame frame) {
+        mAutoReleaseFrames.add(frame);
+    }
+
+    final long getCurrentTimestamp() {
+        return mCurrentTimestamp;
+    }
+
+    final void onPulledFrameWithTimestamp(long timestamp) {
+        if (timestamp > mCurrentTimestamp || mCurrentTimestamp == Frame.TIMESTAMP_NOT_SET) {
+            mCurrentTimestamp = timestamp;
+        }
+    }
+
+    final void openOutputPort(OutputPort outPort) {
+        if (outPort.getQueue() == null) {
+            try {
+                FrameQueue.Builder builder = new FrameQueue.Builder();
+                InputPort inPort = outPort.getTarget();
+                outPort.onOpen(builder);
+                inPort.onOpen(builder);
+                Filter targetFilter = inPort.getFilter();
+                String queueName = mName + "[" + outPort.getName() + "] -> " + targetFilter.mName
+                        + "[" + inPort.getName() + "]";
+                FrameQueue queue = builder.build(queueName);
+                outPort.setQueue(queue);
+                inPort.setQueue(queue);
+            } catch (RuntimeException e) {
+                throw new RuntimeException("Could not open output port " + outPort + "!", e);
+            }
+        }
+    }
+
+    final boolean isSleeping() {
+        return mIsSleeping.get();
+    }
+
+    final long getLastScheduleTime() {
+        return mLastScheduleTime ;
+    }
+
+    private final void autoPullInputs() {
+        // [Non-iterator looping]
+        for (int i = 0; i < mConnectedInputPortArray.length; ++i) {
+            InputPort port = mConnectedInputPortArray[i];
+            if (port.hasFrame() && port.isAutoPullEnabled()) {
+                mConnectedInputPortArray[i].pullFrame();
+            }
+        }
+    }
+
+    private final void autoReleaseFrames() {
+        // [Non-iterator looping]
+        for (int i = 0; i < mAutoReleaseFrames.size(); ++i) {
+            mAutoReleaseFrames.get(i).release();
+        }
+        mAutoReleaseFrames.clear();
+    }
+
+    private final InputPort newInputPort(String name) {
+        InputPort result = mConnectedInputPorts.get(name);
+        if (result == null) {
+            Signature.PortInfo info = getSignature().getInputPortInfo(name);
+            result = new InputPort(this, name, info);
+            mConnectedInputPorts.put(name, result);
+        }
+        return result;
+    }
+
+    private final OutputPort newOutputPort(String name) {
+        OutputPort result = mConnectedOutputPorts.get(name);
+        if (result == null) {
+            Signature.PortInfo info = getSignature().getOutputPortInfo(name);
+            result = new OutputPort(this, name, info);
+            mConnectedOutputPorts.put(name, result);
+        }
+        return result;
+    }
+
+    private final void processRequests() {
+        if ((mRequests & REQUEST_FLAG_CLOSE) != 0) {
+            performClose();
+            mRequests = REQUEST_FLAG_NONE;
+        }
+    }
+
+    private void assertIsPaused() {
+        GraphRunner runner = GraphRunner.current();
+        if (runner != null && !runner.isPaused() && !runner.isStopped()) {
+            throw new RuntimeException("Attempting to modify filter state while runner is "
+                + "executing. Please pause or stop the runner first!");
+        }
+    }
+
+    private final void updatePortArrays() {
+        // Copy our port-maps to arrays for faster non-iterator access
+        mConnectedInputPortArray = mConnectedInputPorts.values().toArray(new InputPort[0]);
+        mConnectedOutputPortArray = mConnectedOutputPorts.values().toArray(new OutputPort[0]);
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterFactory.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterFactory.java
new file mode 100644
index 0000000..2c67c79
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterFactory.java
@@ -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.
+ */
+
+
+package androidx.media.filterfw;
+
+import android.util.Log;
+
+import dalvik.system.PathClassLoader;
+
+import java.lang.reflect.Constructor;
+import java.util.HashSet;
+
+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 boolean isFilterAvailable(String className) {
+        return getFilterClass(className) != null;
+    }
+
+    public Filter createFilterByClassName(String className, String filterName, MffContext context) {
+        if (mLogVerbose) Log.v(TAG, "Looking up class " + className);
+        Class<? extends Filter> filterClass = getFilterClass(className);
+        if (filterClass == null) {
+            throw new IllegalArgumentException("Unknown filter class '" + className + "'!");
+        }
+        return createFilterByClass(filterClass, filterName, context);
+    }
+
+    public Filter createFilterByClass(Class<? extends Filter> filterClass,
+            String filterName, MffContext context) {
+        // Look for the correct constructor
+        Constructor<? extends Filter> filterConstructor = null;
+        try {
+            filterConstructor = filterClass.getConstructor(MffContext.class, String.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException("The filter class '" + filterClass
+                + "' does not have a constructor of the form <init>(MffContext, String)!");
+        }
+
+        // Construct the filter
+        Filter filter = null;
+        try {
+            filter = filterConstructor.newInstance(context, filterName);
+        } catch (Throwable t) {
+            throw new RuntimeException("Error creating filter " + filterName + "!", t);
+        }
+
+        if (filter == null) {
+            throw new IllegalArgumentException("Could not construct the filter '"
+                + filterName + "'!");
+        }
+        return filter;
+    }
+
+    private Class<? extends Filter> getFilterClass(String name) {
+        Class<?> filterClass = null;
+
+        // Look for the class in the imported packages
+        for (String packageName : mPackages) {
+            try {
+                if (mLogVerbose) Log.v(TAG, "Trying "+ packageName + "." + name);
+                synchronized(mClassLoaderGuard) {
+                    filterClass = mCurrentClassLoader.loadClass(packageName + "." + name);
+                }
+            } catch (ClassNotFoundException e) {
+                continue;
+            }
+            // Exit loop if class was found.
+            if (filterClass != null) {
+                break;
+            }
+        }
+        Class<? extends Filter> result = null;
+        try {
+            if (filterClass != null) {
+                result = filterClass.asSubclass(Filter.class);
+            }
+        } catch (ClassCastException e) {
+            // Leave result == null
+        }
+        return result;
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterGraph.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterGraph.java
new file mode 100644
index 0000000..7d5ed9f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FilterGraph.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 androidx.media.filterfw;
+
+import android.util.Log;
+import android.view.View;
+import androidx.media.filterpacks.base.BranchFilter;
+import androidx.media.filterpacks.base.FrameSlotSource;
+import androidx.media.filterpacks.base.FrameSlotTarget;
+import androidx.media.filterpacks.base.GraphInputSource;
+import androidx.media.filterpacks.base.GraphOutputTarget;
+import androidx.media.filterpacks.base.ValueTarget;
+import androidx.media.filterpacks.base.ValueTarget.ValueListener;
+import androidx.media.filterpacks.base.VariableSource;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A graph of Filter nodes.
+ *
+ * A FilterGraph instance contains a set of Filter instances connected by their output and input
+ * ports. Every filter belongs to exactly one graph and cannot be moved to another graph.
+ *
+ * FilterGraphs may contain sub-graphs that are dependent on the parent graph. These are typically
+ * used when inserting sub-graphs into MetaFilters. When a parent graph is torn down so are its
+ * sub-graphs. The same applies to flushing frames of a graph.
+ */
+public class FilterGraph {
+
+    private final static boolean DEBUG = false;
+
+    /** The context that this graph lives in */
+    private MffContext mContext;
+
+    /** Map from name of filter to the filter instance */
+    private HashMap<String, Filter> mFilterMap = new HashMap<String, Filter>();
+
+    /** Allows quick access to array of all filters. */
+    private Filter[] mAllFilters = null;
+
+    /** The GraphRunner currently attached to this graph */
+    GraphRunner mRunner;
+
+    /** The set of sub-graphs of this graph */
+    HashSet<FilterGraph> mSubGraphs = new HashSet<FilterGraph>();
+
+    /** The parent graph of this graph, or null it this graph is a root graph. */
+    private FilterGraph mParentGraph;
+
+    public static class Builder {
+
+        /** The context that this builder lives in */
+        private MffContext mContext;
+
+        /** Map from name of filter to the filter instance */
+        private HashMap<String, Filter> mFilterMap = new HashMap<String, Filter>();
+
+        /**
+         * Creates a new builder for specifying a graph structure.
+         * @param context The context the graph will live in.
+         */
+        public Builder(MffContext context) {
+            mContext = context;
+        }
+
+        /**
+         * Add a filter to the graph.
+         *
+         * Adds the specified filter to the set of filters of this graph. The filter must not be in
+         * the graph already, and the filter's name must be unique within the graph.
+         *
+         * @param filter the filter to add to the graph.
+         * @throws IllegalArgumentException if the filter is in the graph already, or its name is
+         *                                  is already taken.
+         */
+        public void addFilter(Filter filter) {
+            if (mFilterMap.values().contains(filter)) {
+                throw new IllegalArgumentException("Attempting to add filter " + filter + " that "
+                    + "is in the graph already!");
+            } else if (mFilterMap.containsKey(filter.getName())) {
+                throw new IllegalArgumentException("Graph contains filter with name '"
+                    + filter.getName() + "' already!");
+            } else {
+                mFilterMap.put(filter.getName(), filter);
+            }
+        }
+
+        /**
+         * Adds a variable to the graph.
+         *
+         * TODO: More documentation.
+         *
+         * @param name the name of the variable.
+         * @param value the value of the variable or null if no value is to be set yet.
+         * @return the VariableSource filter that holds the value of this variable.
+         */
+        public VariableSource addVariable(String name, Object value) {
+            if (getFilter(name) != null) {
+                throw new IllegalArgumentException("Filter named '" + name + "' exists already!");
+            }
+            VariableSource valueSource = new VariableSource(mContext, name);
+            addFilter(valueSource);
+            if (value != null) {
+                valueSource.setValue(value);
+            }
+            return valueSource;
+        }
+
+        public FrameSlotSource addFrameSlotSource(String name, String slotName) {
+            FrameSlotSource filter = new FrameSlotSource(mContext, name, slotName);
+            addFilter(filter);
+            return filter;
+        }
+
+        public FrameSlotTarget addFrameSlotTarget(String name, String slotName) {
+            FrameSlotTarget filter = new FrameSlotTarget(mContext, name, slotName);
+            addFilter(filter);
+            return filter;
+        }
+
+        /**
+         * Connect two filters by their ports.
+         * The filters specified must have been previously added to the graph builder.
+         *
+         * @param sourceFilterName The name of the source filter.
+         * @param sourcePort The name of the source port.
+         * @param targetFilterName The name of the target filter.
+         * @param targetPort The name of the target port.
+         */
+        public void connect(String sourceFilterName, String sourcePort,
+                            String targetFilterName, String targetPort) {
+            Filter sourceFilter = getFilter(sourceFilterName);
+            Filter targetFilter = getFilter(targetFilterName);
+            if (sourceFilter == null) {
+                throw new IllegalArgumentException("Unknown filter '" + sourceFilterName + "'!");
+            } else if (targetFilter == null) {
+                throw new IllegalArgumentException("Unknown filter '" + targetFilterName + "'!");
+            }
+            connect(sourceFilter, sourcePort, targetFilter, targetPort);
+        }
+
+        /**
+         * Connect two filters by their ports.
+         * The filters specified must have been previously added to the graph builder.
+         *
+         * @param sourceFilter The source filter.
+         * @param sourcePort The name of the source port.
+         * @param targetFilter The target filter.
+         * @param targetPort The name of the target port.
+         */
+        public void connect(Filter sourceFilter, String sourcePort,
+                            Filter targetFilter, String targetPort) {
+            sourceFilter.connect(sourcePort, targetFilter, targetPort);
+        }
+
+        /**
+         * Returns the filter with the specified name.
+         *
+         * @return the filter with the specified name, or null if no such filter exists.
+         */
+        public Filter getFilter(String name) {
+            return mFilterMap.get(name);
+        }
+
+        /**
+         * Builds the graph and checks signatures.
+         *
+         * @return The new graph instance.
+         */
+        public FilterGraph build() {
+            checkSignatures();
+            return buildWithParent(null);
+        }
+
+        /**
+         * Builds the sub-graph and checks signatures.
+         *
+         * @param parentGraph the parent graph of the built sub-graph.
+         * @return The new graph instance.
+         */
+        public FilterGraph buildSubGraph(FilterGraph parentGraph) {
+            if (parentGraph == null) {
+                throw new NullPointerException("Parent graph must be non-null!");
+            }
+            checkSignatures();
+            return buildWithParent(parentGraph);
+        }
+
+        VariableSource assignValueToFilterInput(Object value, String filterName, String inputName) {
+            // Get filter to connect to
+            Filter filter = getFilter(filterName);
+            if (filter == null) {
+                throw new IllegalArgumentException("Unknown filter '" + filterName + "'!");
+            }
+
+            // Construct a name for our value source and make sure it does not exist already
+            String valueSourceName = filterName + "." + inputName;
+            if (getFilter(valueSourceName) != null) {
+                throw new IllegalArgumentException("VariableSource for '" + filterName + "' and "
+                    + "input '" + inputName + "' exists already!");
+            }
+
+            // Create new VariableSource and connect it to the target filter and port
+            VariableSource valueSource = new VariableSource(mContext, valueSourceName);
+            addFilter(valueSource);
+            try {
+                ((Filter)valueSource).connect("value", filter, inputName);
+            } catch (RuntimeException e) {
+                throw new RuntimeException("Could not connect VariableSource to input '" + inputName
+                    + "' of filter '" + filterName + "'!", e);
+            }
+
+            // Assign the value to the VariableSource
+            if (value != null) {
+                valueSource.setValue(value);
+            }
+
+            return valueSource;
+        }
+
+        VariableSource assignVariableToFilterInput(String varName,
+                                                   String filterName,
+                                                   String inputName) {
+            // Get filter to connect to
+            Filter filter = getFilter(filterName);
+            if (filter == null) {
+                throw new IllegalArgumentException("Unknown filter '" + filterName + "'!");
+            }
+
+            // Get variable
+            Filter variable = getFilter(varName);
+            if (variable == null || !(variable instanceof VariableSource)) {
+                throw new IllegalArgumentException("Unknown variable '" + varName + "'!");
+            }
+
+            // Connect variable (and possibly branch) variable to filter
+            try {
+                connectAndBranch(variable, "value", filter, inputName);
+            } catch (RuntimeException e) {
+                throw new RuntimeException("Could not connect VariableSource to input '" + inputName
+                    + "' of filter '" + filterName + "'!", e);
+            }
+
+            return (VariableSource)variable;
+        }
+
+        /**
+         * Builds the graph without checking signatures.
+         * If parent is non-null, build a sub-graph of the specified parent.
+         *
+         * @return The new graph instance.
+         */
+        private FilterGraph buildWithParent(FilterGraph parent) {
+            FilterGraph graph = new FilterGraph(mContext, parent);
+            graph.mFilterMap = mFilterMap;
+            graph.mAllFilters = mFilterMap.values().toArray(new Filter[0]);
+            for (Entry<String, Filter> filterEntry : mFilterMap.entrySet()) {
+                filterEntry.getValue().insertIntoFilterGraph(graph);
+            }
+            return graph;
+        }
+
+        private void checkSignatures() {
+            checkSignaturesForFilters(mFilterMap.values());
+        }
+
+        // TODO: Currently this always branches even if the connection is a 1:1 connection. Later
+        // we may optimize to pass through directly in the 1:1 case (may require disconnecting
+        // ports).
+        private void connectAndBranch(Filter sourceFilter,
+                                      String sourcePort,
+                                      Filter targetFilter,
+                                      String targetPort) {
+            String branchName = "__" + sourceFilter.getName() + "_" + sourcePort + "Branch";
+            Filter branch = getFilter(branchName);
+            if (branch == null) {
+                branch = new BranchFilter(mContext, branchName, false);
+                addFilter(branch);
+                sourceFilter.connect(sourcePort, branch, "input");
+            }
+            String portName = "to" + targetFilter.getName() + "_" + targetPort;
+            branch.connect(portName, targetFilter, targetPort);
+        }
+
+    }
+
+    /**
+     * Attach the graph and its subgraphs to a custom GraphRunner.
+     *
+     * Call this if you want the graph to be executed by a specific GraphRunner. You must call
+     * this before any other runner is set. Note that calls to {@code getRunner()} and
+     * {@code run()} auto-create a GraphRunner.
+     *
+     * @param runner The GraphRunner instance that should execute this graph.
+     * @see #getRunner()
+     * @see #run()
+     */
+    public void attachToRunner(GraphRunner runner) {
+        if (mRunner == null) {
+            for (FilterGraph subGraph : mSubGraphs) {
+                subGraph.attachToRunner(runner);
+            }
+            runner.attachGraph(this);
+            mRunner = runner;
+        } else if (mRunner != runner) {
+            throw new RuntimeException("Cannot attach FilterGraph to GraphRunner that is already "
+                + "attached to another GraphRunner!");
+        }
+    }
+
+    /**
+     * Forcibly tear down a filter graph.
+     *
+     * Call this to release any resources associated with the filter graph, its filters and any of
+     * its sub-graphs. This method must not be called if the graph (or any sub-graph) is running.
+     *
+     * You may no longer access this graph instance or any of its subgraphs after calling this
+     * method.
+     *
+     * Tearing down of sub-graphs is not supported. You must tear down the root graph, which will
+     * tear down all of its sub-graphs.
+     *
+     * @throws IllegalStateException if the graph is still running.
+     * @throws RuntimeException if you attempt to tear down a sub-graph.
+     */
+    public void tearDown() {
+        assertNotRunning();
+        if (mParentGraph != null) {
+            throw new RuntimeException("Attempting to tear down sub-graph!");
+        }
+        if (mRunner != null) {
+            mRunner.tearDownGraph(this);
+        }
+        for (FilterGraph subGraph : mSubGraphs) {
+            subGraph.mParentGraph = null;
+            subGraph.tearDown();
+        }
+        mSubGraphs.clear();
+    }
+
+    /**
+     * Returns the context of the graph.
+     *
+     * @return the MffContext instance that this graph is bound to.
+     */
+    public MffContext getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns the filter with the specified name.
+     *
+     * @return the filter with the specified name, or null if no such filter exists.
+     */
+    public Filter getFilter(String name) {
+        return mFilterMap.get(name);
+    }
+
+    /**
+     * Returns the VariableSource for the specified variable.
+     *
+     * TODO: More documentation.
+     * TODO: More specialized error handling.
+     *
+     * @param name The name of the VariableSource.
+     * @return The VariableSource filter instance with the specified name.
+     */
+    public VariableSource getVariable(String name) {
+        Filter result = mFilterMap.get(name);
+        if (result != null && result instanceof VariableSource) {
+            return (VariableSource)result;
+        } else {
+            throw new IllegalArgumentException("Unknown variable '" + name + "' specified!");
+        }
+    }
+
+    /**
+     * Returns the GraphOutputTarget with the specified name.
+     *
+     * @param name The name of the target.
+     * @return The GraphOutputTarget instance with the specified name.
+     */
+    public GraphOutputTarget getGraphOutput(String name) {
+        Filter result = mFilterMap.get(name);
+        if (result != null && result instanceof GraphOutputTarget) {
+            return (GraphOutputTarget)result;
+        } else {
+            throw new IllegalArgumentException("Unknown target '" + name + "' specified!");
+        }
+    }
+
+    /**
+     * Returns the GraphInputSource with the specified name.
+     *
+     * @param name The name of the source.
+     * @return The GraphInputSource instance with the specified name.
+     */
+    public GraphInputSource getGraphInput(String name) {
+        Filter result = mFilterMap.get(name);
+        if (result != null && result instanceof GraphInputSource) {
+            return (GraphInputSource)result;
+        } else {
+            throw new IllegalArgumentException("Unknown source '" + name + "' specified!");
+        }
+    }
+
+    /**
+     * Binds a filter to a view.
+     *
+     * ViewFilter instances support visualizing their data to a view. See the specific filter
+     * documentation for details. Views may be bound only if the graph is not running.
+     *
+     * @param filterName the name of the filter to bind.
+     * @param view the view to bind to.
+     * @throws IllegalStateException if the filter is in an illegal state.
+     * @throws IllegalArgumentException if no such view-filter exists.
+     */
+    public void bindFilterToView(String filterName, View view) {
+        Filter filter = mFilterMap.get(filterName);
+        if (filter != null && filter instanceof ViewFilter) {
+            ((ViewFilter)filter).bindToView(view);
+        } else {
+            throw new IllegalArgumentException("Unknown view filter '" + filterName + "'!");
+        }
+    }
+
+    /**
+     * TODO: Documentation.
+     */
+    public void bindValueTarget(String filterName, ValueListener listener, boolean onCallerThread) {
+        Filter filter = mFilterMap.get(filterName);
+        if (filter != null && filter instanceof ValueTarget) {
+            ((ValueTarget)filter).setListener(listener, onCallerThread);
+        } else {
+            throw new IllegalArgumentException("Unknown ValueTarget filter '" + filterName + "'!");
+        }
+    }
+
+    // Running Graphs //////////////////////////////////////////////////////////////////////////////
+    /**
+     * Convenience method to run the graph.
+     *
+     * Creates a new runner for this graph in the specified mode and executes it. Returns the
+     * runner to allow control of execution.
+     *
+     * @throws IllegalStateException if the graph is already running.
+     * @return the GraphRunner instance that was used for execution.
+     */
+    public GraphRunner run() {
+        GraphRunner runner = getRunner();
+        runner.setIsVerbose(false);
+        runner.start(this);
+        return runner;
+    }
+
+    /**
+     * Returns the GraphRunner for this graph.
+     *
+     * Every FilterGraph instance has a GraphRunner instance associated with it for executing the
+     * graph.
+     *
+     * @return the GraphRunner instance for this graph.
+     */
+    public GraphRunner getRunner() {
+        if (mRunner == null) {
+            GraphRunner runner = new GraphRunner(mContext);
+            attachToRunner(runner);
+        }
+        return mRunner;
+    }
+
+    /**
+     * Returns whether the graph is currently running.
+     *
+     * @return true if the graph is currently running.
+     */
+    public boolean isRunning() {
+        return mRunner != null && mRunner.isRunning();
+    }
+
+    /**
+     * Check each filter's signatures if all requirements are fulfilled.
+     *
+     * This will throw a RuntimeException if any unfulfilled requirements are found.
+     * Note that FilterGraph.Builder also has a function checkSignatures(), which allows
+     * to do the same /before/ the FilterGraph is built.
+     */
+    public void checkSignatures() {
+        checkSignaturesForFilters(mFilterMap.values());
+    }
+
+    // MFF Internal Methods ////////////////////////////////////////////////////////////////////////
+    Filter[] getAllFilters() {
+        return mAllFilters;
+    }
+
+    static void checkSignaturesForFilters(Collection<Filter> filters) {
+        for (Filter filter : filters) {
+            if (DEBUG) {
+                Log.d("FilterGraph", "Checking filter " + filter.getName() + "...");
+            }
+            Signature signature = filter.getSignature();
+            signature.checkInputPortsConform(filter);
+            signature.checkOutputPortsConform(filter);
+        }
+    }
+
+    /**
+     * Wipes the filter references in this graph, so that they may be collected.
+     *
+     * This must be called only after a tearDown as this will make the FilterGraph invalid.
+     */
+    void wipe() {
+        mAllFilters = null;
+        mFilterMap = null;
+    }
+
+    void flushFrames() {
+        for (Filter filter : mFilterMap.values()) {
+            for (InputPort inputPort : filter.getConnectedInputPorts()) {
+                inputPort.clear();
+            }
+            for (OutputPort outputPort : filter.getConnectedOutputPorts()) {
+                outputPort.clear();
+            }
+        }
+    }
+
+    Set<FilterGraph> getSubGraphs() {
+        return mSubGraphs;
+    }
+
+    // Internal Methods ////////////////////////////////////////////////////////////////////////////
+    private FilterGraph(MffContext context, FilterGraph parentGraph) {
+        mContext = context;
+        mContext.addGraph(this);
+        if (parentGraph != null) {
+            mParentGraph = parentGraph;
+            mParentGraph.mSubGraphs.add(this);
+        }
+    }
+
+    private void assertNotRunning() {
+        if (isRunning()) {
+            throw new IllegalStateException("Attempting to modify running graph!");
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Frame.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Frame.java
new file mode 100644
index 0000000..67907d3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Frame.java
@@ -0,0 +1,203 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import java.util.Arrays;
+
+/**
+ * Frames are the data containers that are transported between Filters.
+ *
+ * Frames may be used only within a Filter during filter graph execution. Accessing Frames outside
+ * of graph execution may cause unexpected results.
+ *
+ * There are two ways to obtain new Frame instances. You can call
+ * {@link OutputPort#fetchAvailableFrame(int[])} on an OutputPort to obtain a Frame to pass to an
+ * output. You can also call {@link #create(FrameType, int[])} to obtain
+ * a detached Frame instance that you may hold onto in your filter. If you need to hold on to a
+ * Frame that is owned by an input or output queue, you must call
+ * {@link #retain()} on it.
+ *
+ * When you are done using a detached Frame, you must release it yourself.
+ *
+ * To access frame data, call any of the {@code lock}-methods. This will give you access to the
+ * frame data in the desired format. You must pass in a {@code mode} indicating whether you wish
+ * to read or write to the data. Writing to a read-locked Frame may produce unexpected results and
+ * interfere with other filters. When you are done reading or writing to the data, you must call
+ * {@link #unlock()}. Note, that a Frame must be unlocked before you push it into an output queue.
+ *
+ * Generally, any type of access format to a Frame's data will be granted. However, it is strongly
+ * recommended to specify the access format that you intend to use in your filter's signature or
+ * in the access flags passed to {@code newFrame()}. This will allow the Frame to allocate
+ * the most efficient backings for the intended type of access.
+ *
+ * A frame can be be pushed to an OutputPort by calling the {@link OutputPort#pushFrame(Frame)}
+ * method. Frames that have been pushed become read-only, and can no longer be modified.
+ *
+ * On the other end, a Filter can pull in an input Frame by calling {@link InputPort#pullFrame()}
+ * on the desired InputPort. Such frames are always read-only.
+ */
+public class Frame {
+
+    /** Special timestamp value indicating that no time-stamp was set. */
+    public static final long TIMESTAMP_NOT_SET = -1;
+
+    /** Frame data access mode: Read */
+    public static final int MODE_READ = 1;
+    /** Frame data access mode: Write */
+    public static final int MODE_WRITE = 2;
+
+    BackingStore mBackingStore;
+    boolean mReadOnly = false;
+
+    // Public API //////////////////////////////////////////////////////////////////////////////////
+    /**
+     * Returns the frame's type.
+     * @return A FrameType instance describing the frame data-type.
+     */
+    public final FrameType getType() {
+        return mBackingStore.getFrameType();
+    }
+
+    public final int getElementCount() {
+        return mBackingStore.getElementCount();
+    }
+
+    /**
+     * Set the frame's timestamp in nanoseconds.
+     *
+     * @param timestamp the timestamp of this frame in nanoseconds.
+     */
+    public final void setTimestamp(long timestamp) {
+        mBackingStore.setTimestamp(timestamp);
+    }
+
+    /**
+     * @return the frame's timestamp in nanoseconds.
+     */
+    public final long getTimestamp() {
+        return mBackingStore.getTimestamp();
+    }
+
+    /**
+     * @return the frame's timestamp in milliseconds.
+     */
+    public final long getTimestampMillis() {
+        return mBackingStore.getTimestamp() / 1000000L;
+    }
+
+    public final boolean isReadOnly() {
+        return mReadOnly;
+    }
+
+    public final FrameValue asFrameValue() {
+        return FrameValue.create(mBackingStore);
+    }
+
+    public final FrameValues asFrameValues() {
+        return FrameValues.create(mBackingStore);
+    }
+
+    public final FrameBuffer1D asFrameBuffer1D() {
+        return FrameBuffer1D.create(mBackingStore);
+    }
+
+    public final FrameBuffer2D asFrameBuffer2D() {
+        return FrameBuffer2D.create(mBackingStore);
+    }
+
+    public final FrameImage2D asFrameImage2D() {
+        return FrameImage2D.create(mBackingStore);
+    }
+
+    @Override
+    public String toString() {
+        return "Frame[" + getType().toString() + "]: " + mBackingStore;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        return object instanceof Frame && ((Frame)object).mBackingStore == mBackingStore;
+    }
+
+    public static Frame create(FrameType type, int[] dimensions) {
+        FrameManager manager = FrameManager.current();
+        if (manager == null) {
+            throw new IllegalStateException("Attempting to create new Frame outside of "
+                + "FrameManager context!");
+        }
+        return new Frame(type, dimensions, manager);
+    }
+
+    public final Frame release() {
+        mBackingStore = mBackingStore.release();
+        return mBackingStore != null ? this : null;
+    }
+
+    public final Frame retain() {
+        mBackingStore = mBackingStore.retain();
+        return this;
+    }
+
+    public void unlock() {
+        if (!mBackingStore.unlock()) {
+            throw new RuntimeException("Attempting to unlock frame that is not locked!");
+        }
+    }
+
+    public int[] getDimensions() {
+        int[] dim = mBackingStore.getDimensions();
+        return dim != null ? Arrays.copyOf(dim, dim.length) : null;
+    }
+
+    Frame(FrameType type, int[] dimensions, FrameManager manager) {
+        mBackingStore = new BackingStore(type, dimensions, manager);
+    }
+
+    Frame(BackingStore backingStore) {
+        mBackingStore = backingStore;
+    }
+
+    final void assertAccessible(int mode) {
+        // Make sure frame is in write-mode
+        if (mReadOnly && mode == MODE_WRITE) {
+            throw new RuntimeException("Attempting to write to read-only frame " + this + "!");
+        }
+    }
+
+    final void setReadOnly(boolean readOnly) {
+        mReadOnly = readOnly;
+    }
+
+    void resize(int[] newDims) {
+        int[] oldDims = mBackingStore.getDimensions();
+        int oldCount = oldDims == null ? 0 : oldDims.length;
+        int newCount = newDims == null ? 0 : newDims.length;
+        if (oldCount != newCount) {
+            throw new IllegalArgumentException("Cannot resize " + oldCount + "-dimensional "
+                + "Frame to " + newCount + "-dimensional Frame!");
+        } else if (newDims != null && !Arrays.equals(oldDims, newDims)) {
+            mBackingStore.resize(newDims);
+        }
+    }
+
+    Frame makeCpuCopy(FrameManager frameManager) {
+        Frame frame = new Frame(getType(), getDimensions(), frameManager);
+        frame.mBackingStore.importStore(mBackingStore);
+        return frame;
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer1D.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer1D.java
new file mode 100644
index 0000000..0e24f5b
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer1D.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 androidx.media.filterfw;
+
+import android.annotation.TargetApi;
+import android.renderscript.Allocation;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class FrameBuffer1D extends Frame {
+
+    private int mLength = 0;
+
+    /**
+     * Access frame's data using a {@link ByteBuffer}.
+     * This is a convenience method and is equivalent to calling {@code lockData} with an
+     * {@code accessFormat} of {@code ACCESS_BYTES}.
+     * When writing to the {@link ByteBuffer}, the byte order should be always set to
+     * {@link ByteOrder#nativeOrder()}.
+     *
+     * @return The byte buffer instance holding the Frame's data.
+     */
+    public ByteBuffer lockBytes(int mode) {
+        assertAccessible(mode);
+        return (ByteBuffer)mBackingStore.lockData(mode, BackingStore.ACCESS_BYTES);
+    }
+
+    /**
+     * Access frame's data using a RenderScript {@link Allocation}.
+     * This is a convenience method and is equivalent to calling {@code lockData} with an
+     * {@code accessFormat} of {@code ACCESS_ALLOCATION}.
+     *
+     * @return The Allocation instance holding the Frame's data.
+     */
+    @TargetApi(11)
+    public Allocation lockAllocation(int mode) {
+        assertAccessible(mode);
+        return (Allocation) mBackingStore.lockData(mode, BackingStore.ACCESS_ALLOCATION);
+    }
+
+    public int getLength() {
+        return mLength;
+    }
+
+    @Override
+    public int[] getDimensions() {
+        return super.getDimensions();
+    }
+
+    /**
+     * TODO: Documentation. Note that frame contents are invalidated.
+     */
+    @Override
+    public void resize(int[] newDimensions) {
+        super.resize(newDimensions);
+    }
+
+    static FrameBuffer1D create(BackingStore backingStore) {
+        assertCanCreate(backingStore);
+        return new FrameBuffer1D(backingStore);
+    }
+
+    FrameBuffer1D(BackingStore backingStore) {
+        super(backingStore);
+        updateLength(backingStore.getDimensions());
+    }
+
+    static void assertCanCreate(BackingStore backingStore) {
+        FrameType type = backingStore.getFrameType();
+        if (type.getElementSize() == 0) {
+            throw new RuntimeException("Cannot access Frame of type " + type + " as a FrameBuffer "
+                + "instance!");
+        }
+        int[] dims = backingStore.getDimensions();
+        if (dims == null || dims.length == 0) {
+            throw new RuntimeException("Cannot access Frame with no dimensions as a FrameBuffer "
+                + "instance!");
+        }
+    }
+
+    void updateLength(int[] dimensions) {
+        mLength = 1;
+        for (int dim : dimensions) {
+            mLength *= dim;
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer2D.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer2D.java
new file mode 100644
index 0000000..6a7f12a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameBuffer2D.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 androidx.media.filterfw;
+
+public class FrameBuffer2D extends FrameBuffer1D {
+
+    public int getWidth() {
+        return mBackingStore.getDimensions()[0];
+    }
+
+    public int getHeight() {
+        return mBackingStore.getDimensions()[1];
+    }
+
+    static FrameBuffer2D create(BackingStore backingStore) {
+        assertCanCreate(backingStore);
+        return new FrameBuffer2D(backingStore);
+    }
+
+    FrameBuffer2D(BackingStore backingStore) {
+        super(backingStore);
+    }
+
+    static void assertCanCreate(BackingStore backingStore) {
+        FrameBuffer1D.assertCanCreate(backingStore);
+        int[] dimensions = backingStore.getDimensions();
+        int dimCount = dimensions != null ? dimensions.length : 0;
+        if (dimCount != 2) {
+            throw new RuntimeException("Cannot access " + dimCount + "-dimensional Frame as a "
+                + "FrameBuffer2D instance!");
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameImage2D.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameImage2D.java
new file mode 100644
index 0000000..bca94f7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameImage2D.java
@@ -0,0 +1,184 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import androidx.media.filterfw.BackingStore.Backing;
+
+public class FrameImage2D extends FrameBuffer2D {
+
+    /**
+     * Access frame's data using a TextureSource.
+     * This is a convenience method and is equivalent to calling {@code lockData} with an
+     * {@code accessFormat} of {@code ACCESS_TEXTURE}.
+     *
+     * @return The TextureSource instance holding the Frame's data.
+     */
+    public TextureSource lockTextureSource() {
+        return (TextureSource)mBackingStore.lockData(MODE_READ, BackingStore.ACCESS_TEXTURE);
+    }
+
+    /**
+     * Access frame's data using a RenderTarget.
+     * This is a convenience method and is equivalent to calling {@code lockData} with an
+     * {@code accessFormat} of {@code ACCESS_RENDERTARGET}.
+     *
+     * @return The RenderTarget instance holding the Frame's data.
+     */
+    public RenderTarget lockRenderTarget() {
+        return (RenderTarget)mBackingStore.lockData(MODE_WRITE, BackingStore.ACCESS_RENDERTARGET);
+    }
+
+    /**
+     * Assigns the pixel data of the specified bitmap.
+     *
+     * The RGBA pixel data will be extracted from the bitmap and assigned to the frame data. Note,
+     * that the colors are premultiplied with the alpha channel. If you wish to have
+     * non-premultiplied colors, you must pass the Frame through an
+     * {@code UnpremultiplyAlphaFilter}.
+     *
+     * @param bitmap The bitmap pixels to assign.
+     */
+    public void setBitmap(Bitmap bitmap) {
+        bitmap = convertToFrameType(bitmap, mBackingStore.getFrameType());
+        validateBitmapSize(bitmap, mBackingStore.getDimensions());
+        Backing backing = mBackingStore.lockBacking(MODE_WRITE, BackingStore.ACCESS_BITMAP);
+        backing.setData(bitmap);
+        mBackingStore.unlock();
+    }
+
+    /**
+     * Returns the RGBA image contents as a Bitmap instance.
+     *
+     * @return a Bitmap instance holding the RGBA Frame image content.
+     */
+    public Bitmap toBitmap() {
+        Bitmap result = (Bitmap)mBackingStore.lockData(MODE_READ, BackingStore.ACCESS_BITMAP);
+        mBackingStore.unlock();
+        return result;
+    }
+
+    /**
+     * Copies the image data from one frame to another.
+     *
+     * The source and target rectangles must be given in normalized coordinates, where 0,0 is the
+     * top-left of the image and 1,1 is the bottom-right.
+     *
+     * If the target rectangle is smaller than the target frame, the pixel values outside of the
+     * target rectangle are undefined.
+     *
+     * This method must be called within a Filter during execution. It supports both GL-enabled
+     * and GL-disabled run contexts.
+     *
+     * @param target The target frame to copy to.
+     * @param sourceRect The source rectangle in normalized coordinates.
+     * @param targetRect The target rectangle in normalized coordinates.
+     */
+    public void copyToFrame(FrameImage2D target, RectF sourceRect, RectF targetRect) {
+        if (GraphRunner.current().isOpenGLSupported()) {
+            gpuImageCopy(this, target, sourceRect, targetRect);
+        } else {
+            cpuImageCopy(this, target, sourceRect, targetRect);
+        }
+    }
+
+    static FrameImage2D create(BackingStore backingStore) {
+        assertCanCreate(backingStore);
+        return new FrameImage2D(backingStore);
+    }
+
+    FrameImage2D(BackingStore backingStore) {
+        super(backingStore);
+    }
+
+    static void assertCanCreate(BackingStore backingStore) {
+        FrameBuffer2D.assertCanCreate(backingStore);
+    }
+
+    private static Bitmap convertToFrameType(Bitmap bitmap, FrameType type) {
+        Bitmap.Config config = bitmap.getConfig();
+        Bitmap result = bitmap;
+        switch(type.getElementId()) {
+            case FrameType.ELEMENT_RGBA8888:
+                if (config != Bitmap.Config.ARGB_8888) {
+                    result = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+                    if (result == null) {
+                        throw new RuntimeException("Could not convert bitmap to frame-type " +
+                                "RGBA8888!");
+                    }
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported frame type '" + type + "' for " +
+                        "bitmap assignment!");
+        }
+        return result;
+    }
+
+    private void validateBitmapSize(Bitmap bitmap, int[] dimensions) {
+        if (bitmap.getWidth() != dimensions[0] || bitmap.getHeight() != dimensions[1]) {
+            throw new IllegalArgumentException("Cannot assign bitmap of size " + bitmap.getWidth()
+                    + "x" + bitmap.getHeight() + " to frame of size " + dimensions[0] + "x"
+                    + dimensions[1] + "!");
+        }
+    }
+
+    private static void gpuImageCopy(
+            FrameImage2D srcImage, FrameImage2D dstImage, RectF srcRect, RectF dstRect) {
+        ImageShader idShader = RenderTarget.currentTarget().getIdentityShader();
+        // We briefly modify the shader
+        // TODO: Implement a safer way to save and restore a shared shader.
+        idShader.setSourceRect(srcRect);
+        idShader.setTargetRect(dstRect);
+        idShader.process(srcImage, dstImage);
+        // And reset it as others may use it as well
+        idShader.setSourceRect(0f, 0f, 1f, 1f);
+        idShader.setTargetRect(0f, 0f, 1f, 1f);
+    }
+
+    private static void cpuImageCopy(
+            FrameImage2D srcImage, FrameImage2D dstImage, RectF srcRect, RectF dstRect) {
+        // Convert rectangles to integer rectangles in image dimensions
+        Rect srcIRect = new Rect((int) srcRect.left * srcImage.getWidth(),
+                (int) srcRect.top * srcImage.getHeight(),
+                (int) srcRect.right * srcImage.getWidth(),
+                (int) srcRect.bottom * srcImage.getHeight());
+        Rect dstIRect = new Rect((int) dstRect.left * srcImage.getWidth(),
+                (int) dstRect.top * srcImage.getHeight(),
+                (int) dstRect.right * srcImage.getWidth(),
+                (int) dstRect.bottom * srcImage.getHeight());
+
+        // Create target canvas
+        Bitmap.Config config = Bitmap.Config.ARGB_8888;
+        Bitmap dstBitmap = Bitmap.createBitmap(dstImage.getWidth(), dstImage.getHeight(), config);
+        Canvas canvas = new Canvas(dstBitmap);
+
+        // Draw source bitmap into target canvas
+        Paint paint = new Paint();
+        paint.setFilterBitmap(true);
+        Bitmap srcBitmap = srcImage.toBitmap();
+        canvas.drawBitmap(srcBitmap, srcIRect, dstIRect, paint);
+
+        // Assign bitmap to output frame
+        dstImage.setBitmap(dstBitmap);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameManager.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameManager.java
new file mode 100644
index 0000000..55ed277
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameManager.java
@@ -0,0 +1,473 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import androidx.media.filterfw.BackingStore.Backing;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+/**
+ * The FrameManager tracks, caches, allocates and deallocates frame data.
+ * All Frame instances are managed by a FrameManager, and belong to exactly one of these. Frames
+ * cannot be shared across FrameManager instances, however multiple MffContexts may use the same
+ * FrameManager.
+ *
+ * Additionally, frame managers allow attaching Frames under a specified key. This allows decoupling
+ * filter-graphs by instructing one node to attach a frame under a specific key, and another to
+ * fetch the frame under the same key.
+ */
+public class FrameManager {
+
+    /** The default max cache size is set to 12 MB */
+    public final static int DEFAULT_MAX_CACHE_SIZE = 12 * 1024 * 1024;
+
+    /** Frame caching policy: No caching */
+    public final static int FRAME_CACHE_NONE = 0;
+    /** Frame caching policy: Drop least recently used frame buffers */
+    public final static int FRAME_CACHE_LRU = 1;
+    /** Frame caching policy: Drop least frequently used frame buffers */
+    public final static int FRAME_CACHE_LFU = 2;
+
+    /** Slot Flag: No flags set */
+    public final static int SLOT_FLAGS_NONE = 0x00;
+    /** Slot Flag: Sticky flag set: Frame will remain in slot after fetch. */
+    public final static int SLOT_FLAG_STICKY = 0x01;
+
+    private GraphRunner mRunner;
+    private Set<Backing> mBackings = new HashSet<Backing>();
+    private BackingCache mCache;
+
+    private Map<String, FrameSlot> mFrameSlots = new HashMap<String, FrameSlot>();
+
+    static class FrameSlot {
+        private FrameType mType;
+        private int mFlags;
+        private Frame mFrame = null;
+
+        public FrameSlot(FrameType type, int flags) {
+            mType = type;
+            mFlags = flags;
+        }
+
+        public FrameType getType() {
+            return mType;
+        }
+
+        public boolean hasFrame() {
+            return mFrame != null;
+        }
+
+        public void releaseFrame() {
+            if (mFrame != null) {
+                mFrame.release();
+                mFrame = null;
+            }
+        }
+
+        // TODO: Type check
+        public void assignFrame(Frame frame) {
+            Frame oldFrame = mFrame;
+            mFrame = frame.retain();
+            if (oldFrame != null) {
+                oldFrame.release();
+            }
+        }
+
+        public Frame getFrame() {
+            Frame result = mFrame.retain();
+            if ((mFlags & SLOT_FLAG_STICKY) == 0) {
+                releaseFrame();
+            }
+            return result;
+        }
+
+        public void markWritable() {
+            if (mFrame != null) {
+                mFrame.setReadOnly(false);
+            }
+        }
+    }
+
+    private static abstract class BackingCache {
+
+        protected int mCacheMaxSize = DEFAULT_MAX_CACHE_SIZE;
+
+        public abstract Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize);
+
+        public abstract boolean cacheBacking(Backing backing);
+
+        public abstract void clear();
+
+        public abstract int getSizeLeft();
+
+        public void setSize(int size) {
+            mCacheMaxSize = size;
+        }
+
+        public int getSize() {
+            return mCacheMaxSize;
+        }
+    }
+
+    private static class BackingCacheNone extends BackingCache {
+
+        @Override
+        public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
+            return null;
+        }
+
+        @Override
+        public boolean cacheBacking(Backing backing) {
+            return false;
+        }
+
+        @Override
+        public void clear() {
+        }
+
+        @Override
+        public int getSize() {
+            return 0;
+        }
+
+        @Override
+        public int getSizeLeft() {
+            return 0;
+        }
+    }
+
+    private static abstract class PriorityBackingCache extends BackingCache {
+        private int mSize = 0;
+        private PriorityQueue<Backing> mQueue;
+
+        public PriorityBackingCache() {
+            mQueue = new PriorityQueue<Backing>(4, new Comparator<Backing>() {
+                @Override
+                public int compare(Backing left, Backing right) {
+                    return left.cachePriority - right.cachePriority;
+                }
+            });
+        }
+
+        @Override
+        public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
+            for (Backing backing : mQueue) {
+                int backingAccess = (mode == Frame.MODE_WRITE)
+                    ? backing.writeAccess()
+                    : backing.readAccess();
+                if ((backingAccess & access) == access
+                    && dimensionsCompatible(backing.getDimensions(), dimensions)
+                    && (elemSize == backing.getElementSize())) {
+                    mQueue.remove(backing);
+                    mSize -= backing.getSize();
+                    onFetchBacking(backing);
+                    return backing;
+                }
+            }
+            //Log.w("FrameManager", "Could not find backing for dimensions " + Arrays.toString(dimensions));
+            return null;
+        }
+
+        @Override
+        public boolean cacheBacking(Backing backing) {
+            if (reserve(backing.getSize())) {
+                onCacheBacking(backing);
+                mQueue.add(backing);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void clear() {
+            mQueue.clear();
+            mSize = 0;
+        }
+
+        @Override
+        public int getSizeLeft() {
+            return mCacheMaxSize - mSize;
+        }
+
+        protected abstract void onCacheBacking(Backing backing);
+
+        protected abstract void onFetchBacking(Backing backing);
+
+        private boolean reserve(int size) {
+            //Log.i("FM", "Reserving " + size + " bytes (max: " + mCacheMaxSize + " bytes).");
+            //Log.i("FM", "Current size " + mSize);
+            if (size > mCacheMaxSize) {
+                return false;
+            }
+            mSize += size;
+            while (mSize > mCacheMaxSize) {
+                Backing dropped = mQueue.poll();
+                mSize -= dropped.getSize();
+                //Log.i("FM", "Dropping  " + dropped + " with priority "
+                //    + dropped.cachePriority + ". New size: " + mSize + "!");
+                dropped.destroy();
+            }
+            return true;
+        }
+
+
+    }
+
+    private static class BackingCacheLru extends PriorityBackingCache {
+        private int mTimestamp = 0;
+
+        @Override
+        protected void onCacheBacking(Backing backing) {
+            backing.cachePriority = 0;
+        }
+
+        @Override
+        protected void onFetchBacking(Backing backing) {
+            ++mTimestamp;
+            backing.cachePriority = mTimestamp;
+        }
+    }
+
+    private static class BackingCacheLfu extends PriorityBackingCache {
+        @Override
+        protected void onCacheBacking(Backing backing) {
+            backing.cachePriority = 0;
+        }
+
+        @Override
+        protected void onFetchBacking(Backing backing) {
+            ++backing.cachePriority;
+        }
+    }
+
+    public static FrameManager current() {
+        GraphRunner runner = GraphRunner.current();
+        return runner != null ? runner.getFrameManager() : null;
+    }
+
+    /**
+     * Returns the context that the FrameManager is bound to.
+     *
+     * @return the MffContext instance that the FrameManager is bound to.
+     */
+    public MffContext getContext() {
+        return mRunner.getContext();
+    }
+
+    /**
+     * Returns the GraphRunner that the FrameManager is bound to.
+     *
+     * @return the GraphRunner instance that the FrameManager is bound to.
+     */
+    public GraphRunner getRunner() {
+        return mRunner;
+    }
+
+    /**
+     * Sets the size of the cache.
+     *
+     * Resizes the cache to the specified size in bytes.
+     *
+     * @param bytes the new size in bytes.
+     */
+    public void setCacheSize(int bytes) {
+        mCache.setSize(bytes);
+    }
+
+    /**
+     * Returns the size of the cache.
+     *
+     * @return the size of the cache in bytes.
+     */
+    public int getCacheSize() {
+        return mCache.getSize();
+    }
+
+    /**
+     * Imports a frame from another FrameManager.
+     *
+     * This will return a frame with the contents of the given frame for use in this FrameManager.
+     * Note, that there is a substantial cost involved in moving a Frame from one FrameManager to
+     * another. This may be called from any thread. After the frame has been imported, it may be
+     * used in the runner that uses this FrameManager. As the new frame may share data with the
+     * provided frame, that frame must be read-only.
+     *
+     * @param frame The frame to import
+     */
+    public Frame importFrame(Frame frame) {
+        if (!frame.isReadOnly()) {
+            throw new IllegalArgumentException("Frame " + frame + " must be read-only to import "
+                    + "into another FrameManager!");
+        }
+        return frame.makeCpuCopy(this);
+    }
+
+    /**
+     * Adds a new frame slot to the frame manager.
+     * Filters can reference frame slots to pass frames between graphs or runs. If the name
+     * specified here is already taken the frame slot is overwritten. You can only
+     * modify frame-slots while no graph of the frame manager is running.
+     *
+     * @param name The name of the slot.
+     * @param type The type of Frame that will be assigned to this slot.
+     * @param flags A mask of {@code SLOT} flags.
+     */
+    public void addFrameSlot(String name, FrameType type, int flags) {
+        assertNotRunning();
+        FrameSlot oldSlot = mFrameSlots.get(name);
+        if (oldSlot != null) {
+            removeFrameSlot(name);
+        }
+        FrameSlot slot = new FrameSlot(type, flags);
+        mFrameSlots.put(name, slot);
+    }
+
+    /**
+     * Removes a frame slot from the frame manager.
+     * Any frame within the slot is released. You can only modify frame-slots while no graph
+     * of the frame manager is running.
+     *
+     * @param name The name of the slot
+     * @throws IllegalArgumentException if no such slot exists.
+     */
+    public void removeFrameSlot(String name) {
+        assertNotRunning();
+        FrameSlot slot = getSlot(name);
+        slot.releaseFrame();
+        mFrameSlots.remove(slot);
+    }
+
+    /**
+     * TODO: Document!
+     */
+    public void storeFrame(Frame frame, String slotName) {
+        assertInGraphRun();
+        getSlot(slotName).assignFrame(frame);
+    }
+
+    /**
+     * TODO: Document!
+     */
+    public Frame fetchFrame(String slotName) {
+        assertInGraphRun();
+        return getSlot(slotName).getFrame();
+    }
+
+    /**
+     * Clears the Frame cache.
+     */
+    public void clearCache() {
+        mCache.clear();
+    }
+
+    /**
+     * Create a new FrameManager instance.
+     *
+     * Creates a new FrameManager instance in the specified context and employing a cache with the
+     * specified cache type (see the cache type constants defined by the FrameManager class).
+     *
+     * @param runner the GraphRunner to bind the FrameManager to.
+     * @param cacheType the type of cache to use.
+     */
+    FrameManager(GraphRunner runner, int cacheType) {
+        mRunner = runner;
+        switch (cacheType) {
+            case FRAME_CACHE_NONE:
+                mCache = new BackingCacheNone();
+                break;
+            case FRAME_CACHE_LRU:
+                mCache = new BackingCacheLru();
+                break;
+            case FRAME_CACHE_LFU:
+                mCache = new BackingCacheLfu();
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown cache-type " + cacheType + "!");
+        }
+    }
+
+    Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
+        return mCache.fetchBacking(mode, access, dimensions, elemSize);
+    }
+
+    void onBackingCreated(Backing backing) {
+        if (backing != null) {
+            mBackings.add(backing);
+            // Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings");
+        }
+    }
+
+    void onBackingAvailable(Backing backing) {
+        if (!backing.shouldCache() || !mCache.cacheBacking(backing)) {
+            backing.destroy();
+            mBackings.remove(backing);
+            //Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings (" + mCache.getSizeLeft() + ")");
+        }
+    }
+
+    /**
+     * Destroying all references makes any Frames that contain them invalid.
+     */
+    void destroyBackings() {
+        for (Backing backing : mBackings) {
+            backing.destroy();
+        }
+        mBackings.clear();
+        mCache.clear();
+    }
+
+    FrameSlot getSlot(String name) {
+        FrameSlot slot = mFrameSlots.get(name);
+        if (slot == null) {
+            throw new IllegalArgumentException("Unknown frame slot '" + name + "'!");
+        }
+        return slot;
+    }
+
+    void onBeginRun() {
+        for (FrameSlot slot : mFrameSlots.values()) {
+            slot.markWritable();
+        }
+    }
+
+    // Internals ///////////////////////////////////////////////////////////////////////////////////
+    private static boolean dimensionsCompatible(int[] dimA, int[] dimB) {
+        return dimA == null || dimB == null || Arrays.equals(dimA, dimB);
+    }
+
+    private void assertNotRunning() {
+        if (mRunner.isRunning()) {
+            throw new IllegalStateException("Attempting to modify FrameManager while graph is "
+                + "running!");
+        }
+    }
+
+    private void assertInGraphRun() {
+        if (!mRunner.isRunning() || GraphRunner.current() != mRunner) {
+            throw new IllegalStateException("Attempting to access FrameManager Frame data "
+                + "outside of graph run-loop!");
+        }
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameQueue.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameQueue.java
new file mode 100644
index 0000000..c26f937
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameQueue.java
@@ -0,0 +1,184 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import java.util.Vector;
+
+class FrameQueue {
+
+    public static class Builder {
+
+        private FrameType mReadType = null;
+        private FrameType mWriteType = null;
+
+        private Vector<FrameQueue> mAttachedQueues = new Vector<FrameQueue>();
+
+        public Builder() {}
+
+        public void setWriteType(FrameType type) {
+            mWriteType = type;
+        }
+
+        public void setReadType(FrameType type) {
+            mReadType = type;
+        }
+
+        public void attachQueue(FrameQueue queue) {
+            mAttachedQueues.add(queue);
+        }
+
+        public FrameQueue build(String name) {
+            FrameType type = buildType();
+            // TODO: This currently does not work correctly (Try camera -> branch -> target-slot)
+            //validateType(type, name);
+            FrameQueue result = new FrameQueue(type, name);
+            buildQueueImpl(result);
+            return result;
+        }
+
+        private void buildQueueImpl(FrameQueue queue) {
+            QueueImpl queueImpl = queue.new SingleFrameQueueImpl();
+            queue.mQueueImpl = queueImpl;
+        }
+
+        private FrameType buildType() {
+            FrameType result = FrameType.merge(mWriteType, mReadType);
+            for (FrameQueue queue : mAttachedQueues) {
+                result = FrameType.merge(result, queue.mType);
+            }
+            return result;
+        }
+
+        /*
+        private void validateType(FrameType type, String queueName) {
+            if (!type.isSpecified()) {
+                throw new RuntimeException("Cannot build connection queue '" + queueName + "' as "
+                        + "its type (" + type + ") is underspecified!");
+            }
+        }
+         */
+    }
+
+    private interface QueueImpl {
+        public boolean canPull();
+
+        public boolean canPush();
+
+        public Frame pullFrame();
+
+        public Frame fetchAvailableFrame(int[] dimensions);
+
+        public Frame peek();
+
+        public void pushFrame(Frame frame);
+
+        public void clear();
+    }
+
+    private class SingleFrameQueueImpl implements QueueImpl {
+        private Frame mFrame = null;
+
+        @Override
+        public boolean canPull() {
+            return mFrame != null;
+        }
+
+        @Override
+        public boolean canPush() {
+            return mFrame == null;
+        }
+
+        @Override
+        public Frame pullFrame() {
+            Frame result = mFrame;
+            mFrame = null;
+            return result;
+        }
+
+        @Override
+        public Frame peek() {
+            return mFrame;
+        }
+
+        @Override
+        public Frame fetchAvailableFrame(int[] dimensions) {
+            // Note that we cannot use a cached frame here, as we do not know where that cached
+            // instance would end up.
+            FrameManager manager = FrameManager.current();
+            return new Frame(mType, dimensions, manager);
+        }
+
+        @Override
+        public void pushFrame(Frame frame) {
+            mFrame = frame.retain();
+            mFrame.setReadOnly(true);
+        }
+
+        @Override
+        public void clear() {
+            if (mFrame != null) {
+                mFrame.release();
+                mFrame = null;
+            }
+        }
+    }
+
+    private QueueImpl mQueueImpl;
+    private FrameType mType;
+    private String mName;
+
+    public FrameType getType() {
+        return mType;
+    }
+
+    public boolean canPull() {
+        return mQueueImpl.canPull();
+    }
+
+    public boolean canPush() {
+        return mQueueImpl.canPush();
+    }
+
+    public Frame pullFrame() {
+        return mQueueImpl.pullFrame();
+    }
+
+    public Frame fetchAvailableFrame(int[] dimensions) {
+        return mQueueImpl.fetchAvailableFrame(dimensions);
+    }
+
+    public void pushFrame(Frame frame) {
+        mQueueImpl.pushFrame(frame);
+    }
+
+    public Frame peek() {
+        return mQueueImpl.peek();
+    }
+
+    @Override
+    public String toString() {
+        return mName;
+    }
+
+    public void clear() {
+        mQueueImpl.clear();
+    }
+
+    private FrameQueue(FrameType type, String name) {
+        mType = type;
+        mName = name;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotSource.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotSource.java
new file mode 100644
index 0000000..0a093f9
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotSource.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 androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.*;
+
+public final class FrameSlotSource extends SlotFilter {
+
+    public FrameSlotSource(MffContext context, String name, String slotName) {
+        super(context, name, slotName);
+    }
+
+    @Override
+    public Signature getSignature() {
+        // TODO: It would be nice if we could return the slot type here. Not currently possible
+        // as getSignature() is typically called before a FrameManager and its slots are setup.
+        return new Signature()
+            .addOutputPort("frame", Signature.PORT_REQUIRED, FrameType.any())
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected boolean canSchedule() {
+        return super.canSchedule() && slotHasFrame();
+    }
+
+    @Override
+    protected void onProcess() {
+        Frame frame = getFrameManager().fetchFrame(mSlotName);
+        getConnectedOutputPort("frame").pushFrame(frame);
+        frame.release();
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotTarget.java
new file mode 100644
index 0000000..55648c6
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameSlotTarget.java
@@ -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.
+ */
+
+package androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.*;
+
+public final class FrameSlotTarget extends SlotFilter {
+
+    public FrameSlotTarget(MffContext context, String name, String slotName) {
+        super(context, name, slotName);
+    }
+
+    @Override
+    public Signature getSignature() {
+        // TODO: It would be nice if we could return the slot type here. Not currently possible
+        // as getSignature() is typically called before a FrameManager and its slots are setup.
+        return new Signature()
+            .addInputPort("frame", Signature.PORT_REQUIRED, FrameType.any())
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        Frame frame = getConnectedInputPort("frame").pullFrame();
+        getFrameManager().storeFrame(frame, mSlotName);
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameType.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameType.java
new file mode 100644
index 0000000..bfa4018
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameType.java
@@ -0,0 +1,430 @@
+/*
+ * 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 androidx.media.filterfw;
+
+
+/**
+ * A FrameType instance specifies the data format of a Frame.
+ *
+ * FrameTypes are used mainly by Filters to specify the data type they intend to consume or produce.
+ * When filters are connected, their FrameType information is analyzed and checked for
+ * compatibility. This allows Filter writers to assume a certain data input type. It also helps
+ * filter-graph designers determine which filters can be hooked up to one another.
+ *
+ * A FrameType generally consists of an element type and number of dimensions. The currently
+ * supported element types are:
+ *
+ * <ul>
+ * <li>int8, int16, int32, in64</li>
+ * <li>float32, float64</li>
+ * <li>rgba8888</li>
+ * <li>object</li>
+ * <li>don't-care</li>
+ * </ul>
+ *
+ * If the object element type is used, class information may be appended to the FrameType to
+ * indicate what class of objects are expected. When constructing an object based FrameType, you
+ * have the option of either specifying a type that represents a single object of that class, or
+ * an array of objects (see the {@link #single()} and {@link #array()} constructors). A single
+ * object has a dimensionality of 0, while an array has a dimensionality of 1.
+ *
+ * When constructing a non-object type, you have the option of creating a 1D or 2D buffer, or
+ * a 2D image (see the {@link #buffer1D(int)}, {@link #buffer2D(int)}, and
+ * {@link #image2D(int, int)} constructors). To optimize access, provide access hints when making
+ * an image type.
+ *
+ * Finally, it is possible to create a wild-card type with the {@link #any()} constructor. This
+ * type matches any other type. Note, that this is a more general type than a {@code single(Object)}
+ * type that matches only object-base types (of any Object subclass). You may also specify the
+ * leave the element of any type unspecified by using the {@code ELEMENT_DONTCARE} constant.
+ *
+ * When a graph is connected the types between outputs and inputs are merged to a queue-type. All
+ * Frames in this queue will be of that type. In order for a merge to succeed the following
+ * conditions must hold:
+ *
+ * <ul>
+ * <li>The element types must be identical.</li>
+ * <li>The dimensions must match (except for singles and arrays, see below).</li>
+ * <li>For object-based types: The classes must be compatible.</li>
+ * <li>If one of the types is a wild-card, both types are always compatible.</li>
+ * </ul>
+ *
+ * Class compatibility is determined in an optimistic fashion, i.e. one class must be the subclass
+ * of the other. It does not matter which of the types is the subclass of the other. For instance,
+ * if one Filter outputs a type of class {@code Object}, and the consumer expects a Filter of type
+ * {@code Bitmap}, the connection is considered compatible. (Of course if at runtime a non-Bitmap
+ * object is produced, this will cause a runtime exception to be thrown).
+ *
+ * For convenience, single and array object-based types are compatible with one another. This
+ * in turn means that Frames with a single object can be accessed as an array with a single entry,
+ * and array based Frames can be accessed as a single object of the array class. For this reason
+ * you should prefer consuming objects as array types (if it makes sense for that specific port),
+ * as this will allow your Filter to handle multiple objects in one Frame while not giving up the
+ * possibility to deal with singles.
+ * TODO: This needs to be reworked. An array(int) should not be interchangeable with a single(int),
+ * but rather with a single(int[]). Use ArraySelectFilter for the former!
+ *
+ * After the types are merged, the queue-type must be a fully specified type. This means that the
+ * type must have its element and dimensions specified. This ensures that filters that need to
+ * query their input or output types receive meaningful information.
+ */
+public final class FrameType {
+
+    public final static int ELEMENT_DONTCARE = 0;
+    public final static int ELEMENT_OBJECT = 1;
+
+    public final static int ELEMENT_INT8 = 100;
+    public final static int ELEMENT_INT16 = 101;
+    public final static int ELEMENT_INT32 = 102;
+    public final static int ELEMENT_INT64 = 103;
+
+    public final static int ELEMENT_FLOAT32 = 200;
+    public final static int ELEMENT_FLOAT64 = 201;
+
+    public final static int ELEMENT_RGBA8888 = 301;
+
+    public final static int READ_CPU = 0x01;
+    public final static int READ_GPU = 0x02;
+    public final static int READ_ALLOCATION = 0x04;
+    public final static int WRITE_CPU = 0x08;
+    public final static int WRITE_GPU = 0x10;
+    public final static int WRITE_ALLOCATION = 0x20;
+
+    private final static int ACCESS_UNKNOWN = 0x00;
+
+    private final int mElementId;
+    private final int mDimensions;
+    private final int mAccessHints;
+    private final Class<?> mClass;
+
+    private static SimpleCache<String, FrameType> mTypeCache =
+            new SimpleCache<String, FrameType>(64);
+
+    /**
+     * Constructs a wild-card FrameType that matches any other FrameType.
+     * @return The wild-card FrameType instance.
+     */
+    public static FrameType any() {
+        return FrameType.fetchType(ELEMENT_DONTCARE, -1, ACCESS_UNKNOWN);
+    }
+
+    /**
+     * Constructs an object-based single FrameType that matches object-based FrameTypes of any
+     * class.
+     * @return A single object-based FrameType instance.
+     */
+    public static FrameType single() {
+        return FrameType.fetchType(null, 0);
+    }
+
+    /**
+     * Constructs an object-based single FrameType of the specified class.
+     * @param clazz The class of the FrameType.
+     * @return A single object-base FrameType instance of the specified class.
+     */
+    public static FrameType single(Class<?> clazz) {
+        return FrameType.fetchType(clazz, 0);
+    }
+
+    /**
+     * Constructs an object-based array FrameType that matches object-based FrameTypes of any class.
+     * @return An array object-based FrameType instance.
+     */
+    public static FrameType array() {
+        return FrameType.fetchType(null, 1);
+    }
+
+    /**
+     * Constructs an object-based array FrameType with elements of the specified class.
+     * @param clazz The class of the array elements (not the array type).
+     * @return An array object-based FrameType instance of the specified class.
+     */
+    public static FrameType array(Class<?> clazz) {
+        return FrameType.fetchType(clazz, 1);
+    }
+
+    /**
+     * Constructs a one-dimensional buffer type of the specified element.
+     * @param elementType One of the {@code ELEMENT} constants.
+     * @return A 1D buffer FrameType instance.
+     */
+    public static FrameType buffer1D(int elementType) {
+        return FrameType.fetchType(elementType, 1, ACCESS_UNKNOWN);
+    }
+
+    /**
+     * Constructs a two-dimensional buffer type of the specified element.
+     * @param elementType One of the {@code ELEMENT} constants.
+     * @return A 2D buffer FrameType instance.
+     */
+    public static FrameType buffer2D(int elementType) {
+        return FrameType.fetchType(elementType, 2, ACCESS_UNKNOWN);
+    }
+
+    /**
+     * Constructs a two-dimensional image type of the specified element.
+     * @param elementType One of the {@code ELEMENT} constants.
+     * @param accessHint A bit-mask of access flags (see {@code READ} and {@code WRITE} constants).
+     * @return A 2D image FrameType instance.
+     */
+    public static FrameType image2D(int elementType, int accessHint) {
+        return FrameType.fetchType(elementType, 2, accessHint);
+    }
+
+    /**
+     * Converts the current array type to a single type.
+     * The type must be an object-based type. If the type is already a single type, this does
+     * nothing.
+     * @return type as a single type.
+     */
+    public FrameType asSingle() {
+        if (mElementId != ELEMENT_OBJECT) {
+            throw new RuntimeException("Calling asSingle() on non-object type!");
+        }
+        return FrameType.fetchType(mClass, 0);
+    }
+
+    /**
+     * Converts the current single type to an array type.
+     * The type must be an object-based type. If the type is already an array type, this does
+     * nothing.
+     * @return type as an array type.
+     */
+    public FrameType asArray() {
+        if (mElementId != ELEMENT_OBJECT) {
+            throw new RuntimeException("Calling asArray() on non-object type!");
+        }
+        return FrameType.fetchType(mClass, 1);
+    }
+
+    /**
+     * Returns the FrameType's class specifier, or null if no class was set or the receiver is not
+     * an object-based type.
+     * @return The FrameType's class specifier or null.
+     */
+    public Class<?> getContentClass() {
+        return mClass;
+    }
+
+    /**
+     * Returns the FrameType's element id.
+     * @return The element id constant.
+     */
+    public int getElementId() {
+        return mElementId;
+    }
+
+    /**
+     * Returns the number of bytes of the FrameType's element, or 0 if no such size can be
+     * determined.
+     * @return The number of bytes of the FrameType's element.
+     */
+    public int getElementSize() {
+        switch (mElementId) {
+            case ELEMENT_INT8:
+                return 1;
+            case ELEMENT_INT16:
+                return 2;
+            case ELEMENT_INT32:
+            case ELEMENT_FLOAT32:
+            case ELEMENT_RGBA8888:
+                return 4;
+            case ELEMENT_INT64:
+            case ELEMENT_FLOAT64:
+                return 4;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Returns the access hints bit-mask of the FrameType.
+     * @return The access hints bit-mask of the FrameType.
+     */
+    public int getAccessHints() {
+        return mAccessHints;
+    }
+
+    /**
+     * Returns the number of dimensions of the FrameType or -1 if no dimensions were set.
+     * @return The number of dimensions of the FrameType.
+     */
+    public int getNumberOfDimensions() {
+        return mDimensions;
+    }
+
+    /**
+     * Returns true, if the FrameType is fully specified.
+     *
+     * A FrameType is fully specified if its element and dimensions are specified.
+     *
+     * @return true, if the FrameType is fully specified.
+     */
+    public boolean isSpecified() {
+        return mElementId != ELEMENT_DONTCARE && mDimensions >= 0;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof FrameType) {
+            FrameType type = (FrameType) object;
+            return mElementId == type.mElementId && mDimensions == type.mDimensions
+                    && mAccessHints == type.mAccessHints && mClass == type.mClass;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mElementId ^ mDimensions ^ mAccessHints ^ mClass.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        String result = elementToString(mElementId, mClass) + "[" + mDimensions + "]";
+        if ((mAccessHints & READ_CPU) != 0) {
+            result += "(rcpu)";
+        }
+        if ((mAccessHints & READ_GPU) != 0) {
+            result += "(rgpu)";
+        }
+        if ((mAccessHints & READ_ALLOCATION) != 0) {
+            result += "(ralloc)";
+        }
+        if ((mAccessHints & WRITE_CPU) != 0) {
+            result += "(wcpu)";
+        }
+        if ((mAccessHints & WRITE_GPU) != 0) {
+            result += "(wgpu)";
+        }
+        if ((mAccessHints & WRITE_ALLOCATION) != 0) {
+            result += "(walloc)";
+        }
+        return result;
+    }
+
+    String keyString() {
+        return keyValueForType(mElementId, mDimensions, mAccessHints, mClass);
+    }
+
+    static FrameType tryMerge(FrameType writer, FrameType reader) {
+        if (writer.mElementId == ELEMENT_DONTCARE) {
+            return reader;
+        } else if (reader.mElementId == ELEMENT_DONTCARE) {
+            return writer;
+        } else if (writer.mElementId == ELEMENT_OBJECT && reader.mElementId == ELEMENT_OBJECT) {
+            return tryMergeObjectTypes(writer, reader);
+        } else if (writer.mDimensions > 0 && writer.mElementId == reader.mElementId) {
+            return tryMergeBuffers(writer, reader);
+        } else {
+            return null;
+        }
+    }
+
+    static FrameType tryMergeObjectTypes(FrameType writer, FrameType reader) {
+        int dimensions = Math.max(writer.mDimensions, reader.mDimensions);
+        Class<?> mergedClass = mergeClasses(writer.mClass, reader.mClass);
+        boolean success = mergedClass != null || writer.mClass == null;
+        return success ? FrameType.fetchType(mergedClass, dimensions) : null;
+    }
+
+    static FrameType tryMergeBuffers(FrameType writer, FrameType reader) {
+        if (writer.mDimensions == reader.mDimensions) {
+            int accessHints = writer.mAccessHints | reader.mAccessHints;
+            return FrameType.fetchType(writer.mElementId, writer.mDimensions, accessHints);
+        }
+        return null;
+    }
+
+    static FrameType merge(FrameType writer, FrameType reader) {
+        FrameType result = tryMerge(writer, reader);
+        if (result == null) {
+            throw new RuntimeException(
+                    "Incompatible types in connection: " + writer + " vs. " + reader + "!");
+        }
+        return result;
+    }
+
+    private static String keyValueForType(int elemId, int dims, int hints, Class<?> clazz) {
+        return elemId + ":" + dims + ":" + hints + ":" + (clazz != null ? clazz.getName() : "0");
+    }
+
+    private static String elementToString(int elemId, Class<?> clazz) {
+        switch (elemId) {
+            case ELEMENT_INT8:
+                return "int8";
+            case ELEMENT_INT16:
+                return "int16";
+            case ELEMENT_INT32:
+                return "int32";
+            case ELEMENT_INT64:
+                return "int64";
+            case ELEMENT_FLOAT32:
+                return "float32";
+            case ELEMENT_FLOAT64:
+                return "float64";
+            case ELEMENT_RGBA8888:
+                return "rgba8888";
+            case ELEMENT_OBJECT:
+                return "<" + (clazz == null ? "*" : clazz.getSimpleName()) + ">";
+            case ELEMENT_DONTCARE:
+                return "*";
+            default:
+                return "?";
+        }
+    }
+
+    private static Class<?> mergeClasses(Class<?> classA, Class<?> classB) {
+        // Return the most specialized class.
+        if (classA == null) {
+            return classB;
+        } else if (classB == null) {
+            return classA;
+        } else if (classA.isAssignableFrom(classB)) {
+            return classB;
+        } else if (classB.isAssignableFrom(classA)) {
+            return classA;
+        } else {
+            return null;
+        }
+    }
+
+    private static FrameType fetchType(int elementId, int dimensions, int accessHints) {
+        return fetchType(elementId, dimensions, accessHints, null);
+    }
+
+    private static FrameType fetchType(Class<?> clazz, int dimensions) {
+        return fetchType(ELEMENT_OBJECT, dimensions, ACCESS_UNKNOWN, clazz);
+    }
+
+    private static FrameType fetchType(
+            int elementId, int dimensions, int accessHints, Class<?> clazz) {
+        String typeKey = FrameType.keyValueForType(elementId, dimensions, accessHints, clazz);
+        FrameType type = mTypeCache.get(typeKey);
+        if (type == null) {
+            type = new FrameType(elementId, dimensions, accessHints, clazz);
+            mTypeCache.put(typeKey, type);
+        }
+        return type;
+    }
+
+    private FrameType(int elementId, int dimensions, int accessHints, Class<?> clazz) {
+        mElementId = elementId;
+        mDimensions = dimensions;
+        mClass = clazz;
+        mAccessHints = accessHints;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValue.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValue.java
new file mode 100644
index 0000000..fb007e2
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValue.java
@@ -0,0 +1,50 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import androidx.media.filterfw.BackingStore.Backing;
+
+public class FrameValue extends Frame {
+
+    public Object getValue() {
+        Object result = mBackingStore.lockData(MODE_READ, BackingStore.ACCESS_OBJECT);
+        mBackingStore.unlock();
+        return result;
+    }
+
+    public void setValue(Object value) {
+        Backing backing = mBackingStore.lockBacking(MODE_WRITE, BackingStore.ACCESS_OBJECT);
+        backing.setData(value);
+        mBackingStore.unlock();
+    }
+
+    static FrameValue create(BackingStore backingStore) {
+        assertObjectBased(backingStore.getFrameType());
+        return new FrameValue(backingStore);
+    }
+
+    FrameValue(BackingStore backingStore) {
+        super(backingStore);
+    }
+
+    static void assertObjectBased(FrameType type) {
+        if (type.getElementId() != FrameType.ELEMENT_OBJECT) {
+            throw new RuntimeException("Cannot access non-object based Frame as FrameValue!");
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValues.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValues.java
new file mode 100644
index 0000000..fbddcb1
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/FrameValues.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 androidx.media.filterfw;
+
+import java.lang.reflect.Array;
+
+public class FrameValues extends FrameValue {
+
+    /**
+     * Returns the number of values in the Frame.
+     *
+     * This returns 1, if the Frame value is null, or if the value is not an array.
+     *
+     * @return The number of values in the Frame.
+     */
+    public int getCount() {
+        Object value = super.getValue();
+        if (value == null || !value.getClass().isArray()) {
+            return 1;
+        } else {
+            return Array.getLength(super.getValue());
+        }
+    }
+
+    /**
+     * Returns the values in the Frame as an array.
+     *
+     * Note, that this may be called on Frames that have a non-array object assigned to them. In
+     * that case, this method will wrap the object in an array and return that. This way, filters
+     * can treat any object based frame as arrays.
+     *
+     * @return The array of values in this frame.
+     */
+    public Object getValues() {
+        Object value = super.getValue();
+        if (value == null || value.getClass().isArray()) {
+            return super.getValue();
+        } else {
+            // Allow reading a single as an array.
+            Object[] array = (Object[])Array.newInstance(value.getClass(), 1);
+            array[0] = value;
+            return array;
+        }
+    }
+
+    /**
+     * Returns the value at the specified index.
+     *
+     * In case the value is null or not an array, the index must be 0, and the value itself is
+     * returned.
+     *
+     * @param index The index to access.
+     * @return The value at that index.
+     */
+    public Object getValueAtIndex(int index) {
+        Object value = super.getValue();
+        if (value == null || !value.getClass().isArray()) {
+            if (index != 0) {
+                throw new ArrayIndexOutOfBoundsException(index);
+            } else {
+                return value;
+            }
+        } else {
+            return Array.get(value, index);
+        }
+    }
+
+    /**
+     * Returns the value as a FrameValue at the specified index.
+     *
+     * Use this if you want to access elements as FrameValues. You must release the result when
+     * you are done using it.
+     *
+     * @param index The index to access.
+     * @return The value as a FrameValue at that index (must release).
+     */
+    public FrameValue getFrameValueAtIndex(int index) {
+        Object value = getValueAtIndex(index);
+        FrameValue result = Frame.create(getType().asSingle(), new int[0]).asFrameValue();
+        result.setValue(value);
+        return result;
+    }
+
+    /**
+     * Assign the array of values to the frame.
+     *
+     * You may assign null or a non-array object, which are interpreted as a 1-length array.
+     *
+     * @param values The values to assign to the frame.
+     */
+    public void setValues(Object values) {
+        super.setValue(values);
+    }
+
+    /**
+     * Assign a value at the specified index.
+     *
+     * In case the held value is not an array, the index must be 0, and the object will be replaced
+     * by the new object.
+     *
+     * @param value The value to assign.
+     * @param index The index to assign to.
+     */
+    public void setValueAtIndex(Object value, int index) {
+        super.assertAccessible(MODE_WRITE);
+        Object curValue = super.getValue();
+        if (curValue == null || !curValue.getClass().isArray()) {
+            if (index != 0) {
+                throw new ArrayIndexOutOfBoundsException(index);
+            } else {
+                curValue = value;
+            }
+        } else {
+            Array.set(curValue, index, value);
+        }
+    }
+
+    /**
+     * Assign a FrameValue's value at the specified index.
+     *
+     * This method unpacks the FrameValue and assigns the unpacked value to the specified index.
+     * This does not affect the retain-count of the passed Frame.
+     *
+     * @param frame The frame value to assign.
+     * @param index The index to assign to.
+     */
+    public void setFrameValueAtIndex(FrameValue frame, int index) {
+        Object value = frame.getValue();
+        setValueAtIndex(value, index);
+    }
+
+    static FrameValues create(BackingStore backingStore) {
+        assertObjectBased(backingStore.getFrameType());
+        return new FrameValues(backingStore);
+    }
+
+    FrameValues(BackingStore backingStore) {
+        super(backingStore);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GLToolbox.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GLToolbox.java
new file mode 100644
index 0000000..1c3c7e9
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GLToolbox.java
@@ -0,0 +1,194 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.os.Looper;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * TODO: Make this package-private as RenderTarget and TextureSource should suffice as public
+ * facing OpenGL utilities.
+ * @hide
+ */
+public class GLToolbox {
+
+    public static int textureNone() {
+        return 0;
+    }
+
+    public static boolean isTexture(int texId) {
+        return GLES20.glIsTexture(texId);
+    }
+
+    public static void deleteTexture(int texId) {
+        int[] textures = new int[] { texId };
+        assertNonUiThread("glDeleteTextures");
+        GLES20.glDeleteTextures(1, textures, 0);
+        checkGlError("glDeleteTextures");
+    }
+
+    public static void deleteFbo(int fboId) {
+        int[] fbos = new int[] { fboId };
+        assertNonUiThread("glDeleteFramebuffers");
+        GLES20.glDeleteFramebuffers(1, fbos, 0);
+        checkGlError("glDeleteFramebuffers");
+    }
+
+    public static int generateTexture() {
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+        checkGlError("glGenTextures");
+        return textures[0];
+    }
+
+    public static int generateFbo() {
+        int[] fbos = new int[1];
+        GLES20.glGenFramebuffers(1, fbos, 0);
+        checkGlError("glGenFramebuffers");
+        return fbos[0];
+    }
+
+    public static void readFbo(int fboId, ByteBuffer pixels, int width, int height) {
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
+        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixels);
+        checkGlError("glReadPixels");
+    }
+
+    public static void readTarget(RenderTarget target, ByteBuffer pixels, int width, int height) {
+        target.focus();
+        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixels);
+        checkGlError("glReadPixels");
+    }
+
+    public static int attachedTexture(int fboId) {
+        int[] params = new int[1];
+        GLES20.glGetFramebufferAttachmentParameteriv(
+            GLES20.GL_FRAMEBUFFER,
+            GLES20.GL_COLOR_ATTACHMENT0,
+            GLES20.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
+            params, 0);
+        checkGlError("glGetFramebufferAttachmentParameteriv");
+        return params[0];
+    }
+
+    public static void attachTextureToFbo(int texId, int fboId) {
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
+        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,
+                                      GLES20.GL_COLOR_ATTACHMENT0,
+                                      GLES20.GL_TEXTURE_2D,
+                                      texId,
+                                      0);
+        checkGlError("glFramebufferTexture2D");
+    }
+
+    public static void allocateTexturePixels(int texId, int target, int width, int height) {
+        setTexturePixels(texId, target, (ByteBuffer)null, width, height);
+    }
+
+    public static void setTexturePixels(int texId, int target, Bitmap bitmap) {
+        GLES20.glBindTexture(target, texId);
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+        checkGlError("glTexImage2D");
+        setDefaultTexParams();
+    }
+
+    public static void setTexturePixels(int texId, int target, ByteBuffer pixels,
+                                        int width, int height) {
+        GLES20.glBindTexture(target, texId);
+
+        // For some devices, "pixels" being null causes system error.
+        if (pixels == null) {
+            pixels = ByteBuffer.allocateDirect(width * height * 4);
+        }
+        GLES20.glTexImage2D(target, 0, GLES20.GL_RGBA, width, height, 0,
+                            GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixels);
+        checkGlError("glTexImage2D");
+        setDefaultTexParams();
+    }
+
+    public static void setDefaultTexParams() {
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+                               GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+                               GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+                               GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+                               GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        checkGlError("glTexParameteri");
+    }
+
+    public static int vboNone() {
+        return 0;
+    }
+
+    public static int generateVbo() {
+        int[] vbos = new int[1];
+        GLES20.glGenBuffers(1, vbos, 0);
+        checkGlError("glGenBuffers");
+        return vbos[0];
+    }
+
+    public static void setVboData(int vboId, ByteBuffer data) {
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, data.remaining(), data, GLES20.GL_STATIC_DRAW);
+        checkGlError("glBufferData");
+    }
+
+    public static void setVboFloats(int vboId, float[] values) {
+        int len = values.length * 4;
+        ByteBuffer buffer = ByteBuffer.allocateDirect(len).order(ByteOrder.nativeOrder());
+        setVboData(vboId, buffer);
+    }
+
+    public static boolean isVbo(int vboId) {
+        return GLES20.glIsBuffer(vboId);
+    }
+
+    public static void deleteVbo(int vboId) {
+        int[] buffers = new int[] { vboId };
+        GLES20.glDeleteBuffers(1, buffers, 0);
+        checkGlError("glDeleteBuffers");
+    }
+
+    public static void checkGlError(String operation) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            throw new RuntimeException("GL Operation '" + operation + "' caused error "
+                + Integer.toHexString(error) + "!");
+        }
+    }
+
+    /**
+     * Make sure we are not operating in the UI thread.
+     *
+     * It is often tricky to track down bugs that happen when issuing GL commands in the UI thread.
+     * This is especially true when releasing GL resources. Often this will cause errors much later
+     * on. Therefore we make sure we do not do these dangerous operations on the UI thread.
+     */
+    private static void assertNonUiThread(String operation) {
+        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
+            throw new RuntimeException("Attempting to perform GL operation '" + operation
+                    + "' on UI thread!");
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphExporter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphExporter.java
new file mode 100644
index 0000000..0013965
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphExporter.java
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+// This class provides functions to export a FilterGraph.
+
+package androidx.media.filterfw;
+
+import android.content.Context;
+
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class provides functions to export a FilterGraph as a DOT file.
+ */
+public class GraphExporter {
+
+    /**
+     * Exports the graph as DOT (see http://en.wikipedia.org/wiki/DOT_language).
+     * Using the exported file, the graph can be visualized e.g. with the command line tool dot.
+     * Optionally, one may /exclude/ unconnected optional ports (third parameter = false),
+     * since they can quickly clutter the visualization (and, depending on the purpose, may not
+     * be interesting).
+     *
+     * Example workflow:
+     *  1. run application on device, make sure it calls exportGraphAsDOT(...);
+     *  2. adb pull /data/data/<application name>/files/<graph filename>.gv graph.gv
+     *  3. dot -Tpng graph.gv -o graph.png
+     *  4. eog graph.png
+     */
+    static public void exportAsDot(FilterGraph graph, String filename,
+            boolean includeUnconnectedOptionalPorts)
+            throws java.io.FileNotFoundException, java.io.IOException {
+        // Initialize, open file stream
+        Context myAppContext = graph.getContext().getApplicationContext();
+        Filter[] filters = graph.getAllFilters();
+        FileOutputStream fOut = myAppContext.openFileOutput(filename, Context.MODE_PRIVATE);
+        OutputStreamWriter dotFile = new OutputStreamWriter(fOut);
+
+        // Write beginning of DOT file
+        dotFile.write("digraph graphname {\n");
+        dotFile.write("  node [shape=record];\n");
+
+        // N.B. For specification and lots of examples of the DOT language, see
+        //   http://www.graphviz.org/Documentation/dotguide.pdf
+
+        // Iterate over all filters of the graph, write corresponding DOT node elements
+
+        for(Filter filter : filters) {
+            dotFile.write(getDotName("  " + filter.getName()) + " [label=\"{");
+
+            // Write upper part of element (i.e., input ports)
+            Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
+            if(inputPorts.size() > 0) {
+                dotFile.write(" { ");
+                int counter = 0;
+                for(String p : inputPorts) {
+                    dotFile.write("<" + getDotName(p) + "_IN>" + p);
+                    if(++counter != inputPorts.size()) dotFile.write(" | ");
+                }
+                dotFile.write(" } | ");
+            }
+
+            // Write center part of element (i.e., element label)
+            dotFile.write(filter.getName());
+
+            // Write lower part of element (i.e., output ports)
+            Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
+            if(outputPorts.size() > 0) {
+                dotFile.write(" | { ");
+                int counter = 0;
+                for(String p : outputPorts) {
+                    dotFile.write("<" + getDotName(p) + "_OUT>" + p);
+                    if(++counter != outputPorts.size()) dotFile.write(" | ");
+                }
+                dotFile.write(" } ");
+            }
+
+            dotFile.write("}\"];\n");
+        }
+        dotFile.write("\n");
+
+        // Iterate over all filters again to collect connections and find unconnected ports
+
+        int dummyNodeCounter = 0;
+        for(Filter filter : filters) {
+            Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
+            for(String portName : outputPorts) {
+                OutputPort source = filter.getConnectedOutputPort(portName);
+                if(source != null) {
+                    // Found a connection, draw it
+                    InputPort target = source.getTarget();
+                    dotFile.write("  " +
+                        getDotName(source.getFilter().getName()) + ":" +
+                        getDotName(source.getName()) + "_OUT -> " +
+                        getDotName(target.getFilter().getName()) + ":" +
+                        getDotName(target.getName()) + "_IN;\n" );
+                } else {
+                    // Found a unconnected output port, add dummy node
+                    String color = filter.getSignature().getOutputPortInfo(portName).isRequired()
+                        ? "red" : "blue";  // red for unconnected, required ports
+                    dotFile.write("  " +
+                        "dummy" + (++dummyNodeCounter) +
+                        " [shape=point,label=\"\",color=" + color + "];\n" +
+                        "  " + getDotName(filter.getName()) + ":" +
+                        getDotName(portName) + "_OUT -> " +
+                        "dummy" + dummyNodeCounter + " [color=" + color + "];\n");
+                }
+            }
+
+            Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
+            for(String portName : inputPorts) {
+                InputPort target = filter.getConnectedInputPort(portName);
+                if(target != null) {
+                    // Found a connection -- nothing to do, connections have been written out above
+                } else {
+                    // Found a unconnected input port, add dummy node
+                    String color = filter.getSignature().getInputPortInfo(portName).isRequired()
+                        ? "red" : "blue";  // red for unconnected, required ports
+                    dotFile.write("  " +
+                        "dummy" + (++dummyNodeCounter) +
+                        " [shape=point,label=\"\",color=" + color + "];\n" +
+                        "  dummy" + dummyNodeCounter + " -> " +
+                        getDotName(filter.getName()) + ":" +
+                        getDotName(portName) + "_IN [color=" + color + "];\n");
+                }
+            }
+        }
+
+        // Write end of DOT file, close file stream
+        dotFile.write("}\n");
+        dotFile.flush();
+        dotFile.close();
+    }
+
+    // Internal methods
+
+    // From element's name in XML, create DOT-allowed element name
+    static private String getDotName(String raw) {
+        return raw.replaceAll("\\.", "___"); // DOT does not allow . in element names
+    }
+
+    // Retrieve all input ports of a filter, including:
+    //  unconnected ports (which can not be retrieved from the filter, only from the signature), and
+    //  additional (connected) ports not listed in the signature (which is allowed by default,
+    //    unless disallowOtherInputs is defined in signature).
+    // With second parameter = false, *omit* unconnected optional ports.
+    static private Set<String> getInputPorts(Filter filter, boolean includeUnconnectedOptional) {
+        // add (connected) ports from filter
+        Set<String> ports = new HashSet<String>();
+        ports.addAll(filter.getConnectedInputPortMap().keySet());
+
+        // add (unconnected) ports from signature
+        HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getInputPorts();
+        if(signaturePorts != null){
+            for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
+                if(includeUnconnectedOptional || e.getValue().isRequired()) {
+                    ports.add(e.getKey());
+                }
+            }
+        }
+        return ports;
+    }
+
+    // Retrieve all output ports of a filter (analogous to above function)
+    static private Set<String> getOutputPorts(Filter filter, boolean includeUnconnectedOptional) {
+        // add (connected) ports from filter
+        Set<String> ports = new HashSet<String>();
+        ports.addAll(filter.getConnectedOutputPortMap().keySet());
+
+        // add (unconnected) ports from signature
+        HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getOutputPorts();
+        if(signaturePorts != null){
+            for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
+                if(includeUnconnectedOptional || e.getValue().isRequired()) {
+                    ports.add(e.getKey());
+                }
+            }
+        }
+        return ports;
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphInputSource.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphInputSource.java
new file mode 100644
index 0000000..03b3abe
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphInputSource.java
@@ -0,0 +1,58 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.Signature;
+
+public class GraphInputSource extends Filter {
+
+    private Frame mFrame = null;
+
+    public GraphInputSource(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addOutputPort("frame", Signature.PORT_REQUIRED, FrameType.any())
+            .disallowOtherInputs();
+    }
+
+    public void pushFrame(Frame frame) {
+        if (mFrame != null) {
+            mFrame.release();
+        }
+        if (frame == null) {
+            throw new RuntimeException("Attempting to assign null-frame!");
+        }
+        mFrame = frame.retain();
+    }
+
+    @Override
+    protected void onProcess() {
+        if (mFrame != null) {
+            getConnectedOutputPort("frame").pushFrame(mFrame);
+            mFrame.release();
+            mFrame = null;
+        }
+    }
+
+    @Override
+    protected void onTearDown() {
+        if (mFrame != null) {
+            mFrame.release();
+            mFrame = null;
+        }
+    }
+
+    @Override
+    protected boolean canSchedule() {
+        return super.canSchedule() && mFrame != null;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphOutputTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphOutputTarget.java
new file mode 100644
index 0000000..1f3be10
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphOutputTarget.java
@@ -0,0 +1,60 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.Signature;
+
+public class GraphOutputTarget extends Filter {
+
+    private Frame mFrame = null;
+    private FrameType mType = FrameType.any();
+
+    public GraphOutputTarget(MffContext context, String name) {
+        super(context, name);
+    }
+
+    // TODO: During initialization only?
+    public void setType(FrameType type) {
+        mType = type;
+    }
+
+    public FrameType getType() {
+        return mType;
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("frame", Signature.PORT_REQUIRED, mType)
+            .disallowOtherInputs();
+    }
+
+    // Returns a retained frame!
+    public Frame pullFrame() {
+        Frame result = null;
+        if (mFrame != null) {
+            result = mFrame;
+            mFrame = null;
+        }
+        return result;
+    }
+
+    @Override
+    protected void onProcess() {
+        Frame frame = getConnectedInputPort("frame").pullFrame();
+        if (mFrame != null) {
+            mFrame.release();
+        }
+        mFrame = frame.retain();
+    }
+
+    @Override
+    protected boolean canSchedule() {
+        return super.canSchedule() && mFrame == null;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphReader.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphReader.java
new file mode 100644
index 0000000..ef885e3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphReader.java
@@ -0,0 +1,576 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.text.TextUtils;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A GraphReader allows obtaining filter graphs from XML graph files or strings.
+ */
+public class GraphReader {
+
+    private static interface Command {
+        public void execute(CommandStack stack);
+    }
+
+    private static class CommandStack {
+        private ArrayList<Command> mCommands = new ArrayList<Command>();
+        private FilterGraph.Builder mBuilder;
+        private FilterFactory mFactory;
+        private MffContext mContext;
+
+        public CommandStack(MffContext context) {
+            mContext = context;
+            mBuilder = new FilterGraph.Builder(mContext);
+            mFactory = new FilterFactory();
+        }
+
+        public void execute() {
+            for (Command command : mCommands) {
+                command.execute(this);
+            }
+        }
+
+        public void append(Command command) {
+            mCommands.add(command);
+        }
+
+        public FilterFactory getFactory() {
+            return mFactory;
+        }
+
+        public MffContext getContext() {
+            return mContext;
+        }
+
+        protected FilterGraph.Builder getBuilder() {
+            return mBuilder;
+        }
+    }
+
+    private static class ImportPackageCommand implements Command {
+        private String mPackageName;
+
+        public ImportPackageCommand(String packageName) {
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            try {
+                stack.getFactory().addPackage(mPackageName);
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+    }
+
+    private static class AddLibraryCommand implements Command {
+        private String mLibraryName;
+
+        public AddLibraryCommand(String libraryName) {
+            mLibraryName = libraryName;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            FilterFactory.addFilterLibrary(mLibraryName);
+        }
+    }
+
+    private static class AllocateFilterCommand implements Command {
+        private String mClassName;
+        private String mFilterName;
+
+        public AllocateFilterCommand(String className, String filterName) {
+            mClassName = className;
+            mFilterName = filterName;
+        }
+
+        @Override
+	public void execute(CommandStack stack) {
+            Filter filter = null;
+            try {
+                filter = stack.getFactory().createFilterByClassName(mClassName,
+                                                                    mFilterName,
+                                                                    stack.getContext());
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException("Error creating filter " + mFilterName + "!", e);
+            }
+            stack.getBuilder().addFilter(filter);
+        }
+    }
+
+    private static class AddSourceSlotCommand implements Command {
+        private String mName;
+        private String mSlotName;
+
+        public AddSourceSlotCommand(String name, String slotName) {
+            mName = name;
+            mSlotName = slotName;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            stack.getBuilder().addFrameSlotSource(mName, mSlotName);
+        }
+    }
+
+    private static class AddTargetSlotCommand implements Command {
+        private String mName;
+        private String mSlotName;
+
+        public AddTargetSlotCommand(String name, String slotName) {
+            mName = name;
+            mSlotName = slotName;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            stack.getBuilder().addFrameSlotTarget(mName, mSlotName);
+        }
+    }
+
+    private static class AddVariableCommand implements Command {
+        private String mName;
+        private Object mValue;
+
+        public AddVariableCommand(String name, Object value) {
+            mName = name;
+            mValue = value;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            stack.getBuilder().addVariable(mName, mValue);
+        }
+    }
+
+    private static class SetFilterInputCommand implements Command {
+        private String mFilterName;
+        private String mFilterInput;
+        private Object mValue;
+
+        public SetFilterInputCommand(String filterName, String input, Object value) {
+            mFilterName = filterName;
+            mFilterInput = input;
+            mValue = value;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            if (mValue instanceof Variable) {
+                String varName = ((Variable)mValue).name;
+                stack.getBuilder().assignVariableToFilterInput(varName, mFilterName, mFilterInput);
+            } else {
+                stack.getBuilder().assignValueToFilterInput(mValue, mFilterName, mFilterInput);
+            }
+        }
+    }
+
+    private static class ConnectCommand implements Command {
+        private String mSourceFilter;
+        private String mSourcePort;
+        private String mTargetFilter;
+        private String mTargetPort;
+
+        public ConnectCommand(String sourceFilter,
+                              String sourcePort,
+                              String targetFilter,
+                              String targetPort) {
+            mSourceFilter = sourceFilter;
+            mSourcePort = sourcePort;
+            mTargetFilter = targetFilter;
+            mTargetPort = targetPort;
+        }
+
+        @Override
+        public void execute(CommandStack stack) {
+            stack.getBuilder().connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetPort);
+        }
+    }
+
+    private static class Variable {
+        public String name;
+
+        public Variable(String name) {
+            this.name = name;
+        }
+    }
+
+    private static class XmlGraphReader {
+
+        private SAXParserFactory mParserFactory;
+
+        private static class GraphDataHandler extends DefaultHandler {
+
+            private CommandStack mCommandStack;
+            private boolean mInGraph = false;
+            private String mCurFilterName = null;
+
+            public GraphDataHandler(CommandStack commandStack) {
+                mCommandStack = commandStack;
+            }
+
+            @Override
+            public void startElement(String uri, String localName, String qName, Attributes attr)
+                    throws SAXException {
+                if (localName.equals("graph")) {
+                    beginGraph();
+                } else {
+                    assertInGraph(localName);
+                    if (localName.equals("import")) {
+                        addImportCommand(attr);
+                    } else if (localName.equals("library")) {
+                        addLibraryCommand(attr);
+                    } else if (localName.equals("connect")) {
+                        addConnectCommand(attr);
+                    } else if (localName.equals("var")) {
+                        addVarCommand(attr);
+                    } else if (localName.equals("filter")) {
+                        beginFilter(attr);
+                    } else if (localName.equals("input")) {
+                        addFilterInput(attr);
+                    } else {
+                        throw new SAXException("Unknown XML element '" + localName + "'!");
+                    }
+                }
+            }
+
+            @Override
+            public void endElement (String uri, String localName, String qName) {
+                if (localName.equals("graph")) {
+                    endGraph();
+                } else if (localName.equals("filter")) {
+                    endFilter();
+                }
+            }
+
+            private void addImportCommand(Attributes attributes) throws SAXException {
+                String packageName = getRequiredAttribute(attributes, "package");
+                mCommandStack.append(new ImportPackageCommand(packageName));
+            }
+
+            private void addLibraryCommand(Attributes attributes) throws SAXException {
+                String libraryName = getRequiredAttribute(attributes, "name");
+                mCommandStack.append(new AddLibraryCommand(libraryName));
+            }
+
+            private void addConnectCommand(Attributes attributes) {
+                String sourcePortName   = null;
+                String sourceFilterName = null;
+                String targetPortName   = null;
+                String targetFilterName = null;
+
+                // check for shorthand: <connect source="filter:port" target="filter:port"/>
+                String sourceTag = attributes.getValue("source");
+                if (sourceTag != null) {
+                    String[] sourceParts = sourceTag.split(":");
+                    if (sourceParts.length == 2) {
+                        sourceFilterName = sourceParts[0];
+                        sourcePortName   = sourceParts[1];
+                    } else {
+                        throw new RuntimeException(
+                            "'source' tag needs to have format \"filter:port\"! " +
+                            "Alternatively, you may use the form " +
+                            "'sourceFilter=\"filter\" sourcePort=\"port\"'.");
+                    }
+                } else {
+                    sourceFilterName = attributes.getValue("sourceFilter");
+                    sourcePortName   = attributes.getValue("sourcePort");
+                }
+
+                String targetTag = attributes.getValue("target");
+                if (targetTag != null) {
+                    String[] targetParts = targetTag.split(":");
+                    if (targetParts.length == 2) {
+                        targetFilterName = targetParts[0];
+                        targetPortName   = targetParts[1];
+                    } else {
+                        throw new RuntimeException(
+                            "'target' tag needs to have format \"filter:port\"! " +
+                            "Alternatively, you may use the form " +
+                            "'targetFilter=\"filter\" targetPort=\"port\"'.");
+                    }
+                } else {
+                    targetFilterName = attributes.getValue("targetFilter");
+                    targetPortName   = attributes.getValue("targetPort");
+                }
+
+                String sourceSlotName = attributes.getValue("sourceSlot");
+                String targetSlotName = attributes.getValue("targetSlot");
+                if (sourceSlotName != null) {
+                    sourceFilterName = "sourceSlot_" + sourceSlotName;
+                    mCommandStack.append(new AddSourceSlotCommand(sourceFilterName,
+                                                                  sourceSlotName));
+                    sourcePortName = "frame";
+                }
+                if (targetSlotName != null) {
+                    targetFilterName = "targetSlot_" + targetSlotName;
+                    mCommandStack.append(new AddTargetSlotCommand(targetFilterName,
+                                                                  targetSlotName));
+                    targetPortName = "frame";
+                }
+                assertValueNotNull("sourceFilter", sourceFilterName);
+                assertValueNotNull("sourcePort", sourcePortName);
+                assertValueNotNull("targetFilter", targetFilterName);
+                assertValueNotNull("targetPort", targetPortName);
+                // TODO: Should slot connections auto-branch?
+                mCommandStack.append(new ConnectCommand(sourceFilterName,
+                                                        sourcePortName,
+                                                        targetFilterName,
+                                                        targetPortName));
+            }
+
+            private void addVarCommand(Attributes attributes) throws SAXException {
+                String varName = getRequiredAttribute(attributes, "name");
+                Object varValue = getAssignmentValue(attributes);
+                mCommandStack.append(new AddVariableCommand(varName, varValue));
+            }
+
+            private void beginGraph() throws SAXException {
+                if (mInGraph) {
+                    throw new SAXException("Found more than one graph element in XML!");
+                }
+                mInGraph = true;
+            }
+
+            private void endGraph() {
+                mInGraph = false;
+            }
+
+            private void beginFilter(Attributes attributes) throws SAXException {
+                String className = getRequiredAttribute(attributes, "class");
+                mCurFilterName = getRequiredAttribute(attributes, "name");
+                mCommandStack.append(new AllocateFilterCommand(className, mCurFilterName));
+            }
+
+            private void endFilter() {
+                mCurFilterName = null;
+            }
+
+            private void addFilterInput(Attributes attributes) throws SAXException {
+                // Make sure we are in a filter element
+                if (mCurFilterName == null) {
+                    throw new SAXException("Found 'input' element outside of 'filter' "
+                        + "element!");
+                }
+
+                // Get input name and value
+                String inputName = getRequiredAttribute(attributes, "name");
+                Object inputValue = getAssignmentValue(attributes);
+                if (inputValue == null) {
+                    throw new SAXException("No value specified for input '" + inputName + "' "
+                        + "of filter '" + mCurFilterName + "'!");
+                }
+
+                // Push commmand
+                mCommandStack.append(new SetFilterInputCommand(mCurFilterName,
+                                                               inputName,
+                                                               inputValue));
+            }
+
+            private void assertInGraph(String localName) throws SAXException {
+                if (!mInGraph) {
+                    throw new SAXException("Encountered '" + localName + "' element outside of "
+                        + "'graph' element!");
+                }
+            }
+
+            private static Object getAssignmentValue(Attributes attributes) {
+                String strValue = null;
+                if ((strValue = attributes.getValue("stringValue")) != null) {
+                    return strValue;
+                } else if ((strValue = attributes.getValue("booleanValue")) != null) {
+                    return Boolean.parseBoolean(strValue);
+                } else if ((strValue = attributes.getValue("intValue")) != null) {
+                    return Integer.parseInt(strValue);
+                } else if ((strValue = attributes.getValue("floatValue")) != null) {
+                    return Float.parseFloat(strValue);
+                } else if ((strValue = attributes.getValue("floatsValue")) != null) {
+                    String[] floatStrings = TextUtils.split(strValue, ",");
+                    float[] result = new float[floatStrings.length];
+                    for (int i = 0; i < floatStrings.length; ++i) {
+                        result[i] = Float.parseFloat(floatStrings[i]);
+                    }
+                    return result;
+                } else if ((strValue = attributes.getValue("varValue")) != null) {
+                    return new Variable(strValue);
+                } else {
+                    return null;
+                }
+            }
+
+            private static String getRequiredAttribute(Attributes attributes, String name)
+                    throws SAXException {
+                String result = attributes.getValue(name);
+                if (result == null) {
+                    throw new SAXException("Required attribute '" + name + "' not found!");
+                }
+                return result;
+            }
+
+            private static void assertValueNotNull(String valueName, Object value) {
+                if (value == null) {
+                    throw new NullPointerException("Required value '" + value + "' not specified!");
+                }
+            }
+
+        }
+
+        public XmlGraphReader() {
+            mParserFactory = SAXParserFactory.newInstance();
+        }
+
+        public void parseString(String graphString, CommandStack commandStack) throws IOException {
+            try {
+                XMLReader reader = getReaderForCommandStack(commandStack);
+                reader.parse(new InputSource(new StringReader(graphString)));
+            } catch (SAXException e) {
+                throw new IOException("XML parse error during graph parsing!", e);
+            }
+        }
+
+        public void parseInput(InputStream inputStream, CommandStack commandStack)
+                throws IOException {
+            try {
+                XMLReader reader = getReaderForCommandStack(commandStack);
+                reader.parse(new InputSource(inputStream));
+            } catch (SAXException e) {
+                throw new IOException("XML parse error during graph parsing!", e);
+            }
+        }
+
+        private XMLReader getReaderForCommandStack(CommandStack commandStack) throws IOException {
+            try {
+                SAXParser parser = mParserFactory.newSAXParser();
+                XMLReader reader = parser.getXMLReader();
+                GraphDataHandler graphHandler = new GraphDataHandler(commandStack);
+                reader.setContentHandler(graphHandler);
+                return reader;
+            } catch (ParserConfigurationException e) {
+                throw new IOException("Error creating SAXParser for graph parsing!", e);
+            } catch (SAXException e) {
+                throw new IOException("Error creating XMLReader for graph parsing!", e);
+            }
+        }
+    }
+
+    /**
+     * Read an XML graph from a String.
+     *
+     * This function automatically checks each filters' signatures and throws a Runtime Exception
+     * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
+     *
+     * @param context the MffContext into which to load the graph.
+     * @param xmlSource the graph specified in XML.
+     * @return the FilterGraph instance for the XML source.
+     * @throws IOException if there was an error parsing the source.
+     */
+    public static FilterGraph readXmlGraph(MffContext context, String xmlSource)
+            throws IOException {
+        FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource);
+        return builder.build();
+    }
+
+    /**
+     * Read an XML sub-graph from a String.
+     *
+     * @param context the MffContext into which to load the graph.
+     * @param xmlSource the graph specified in XML.
+     * @param parentGraph the parent graph.
+     * @return the FilterGraph instance for the XML source.
+     * @throws IOException if there was an error parsing the source.
+     */
+    public static FilterGraph readXmlSubGraph(
+            MffContext context, String xmlSource, FilterGraph parentGraph)
+            throws IOException {
+        FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource);
+        return builder.buildSubGraph(parentGraph);
+    }
+
+    /**
+     * Read an XML graph from a resource.
+     *
+     * This function automatically checks each filters' signatures and throws a Runtime Exception
+     * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
+     *
+     * @param context the MffContext into which to load the graph.
+     * @param resourceId the XML resource ID.
+     * @return the FilterGraph instance for the XML source.
+     * @throws IOException if there was an error reading or parsing the resource.
+     */
+    public static FilterGraph readXmlGraphResource(MffContext context, int resourceId)
+            throws IOException {
+        FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId);
+        return builder.build();
+    }
+
+    /**
+     * Read an XML graph from a resource.
+     *
+     * This function automatically checks each filters' signatures and throws a Runtime Exception
+     * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
+     *
+     * @param context the MffContext into which to load the graph.
+     * @param resourceId the XML resource ID.
+     * @return the FilterGraph instance for the XML source.
+     * @throws IOException if there was an error reading or parsing the resource.
+     */
+    public static FilterGraph readXmlSubGraphResource(
+            MffContext context, int resourceId, FilterGraph parentGraph)
+            throws IOException {
+        FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId);
+        return builder.buildSubGraph(parentGraph);
+    }
+
+    private static FilterGraph.Builder getBuilderForXmlString(MffContext context, String source)
+            throws IOException {
+        XmlGraphReader reader = new XmlGraphReader();
+        CommandStack commands = new CommandStack(context);
+        reader.parseString(source, commands);
+        commands.execute();
+        return commands.getBuilder();
+    }
+
+    private static FilterGraph.Builder getBuilderForXmlResource(MffContext context, int resourceId)
+            throws IOException {
+        InputStream inputStream = context.getApplicationContext().getResources()
+                .openRawResource(resourceId);
+        XmlGraphReader reader = new XmlGraphReader();
+        CommandStack commands = new CommandStack(context);
+        reader.parseInput(inputStream, commands);
+        commands.execute();
+        return commands.getBuilder();
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphRunner.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphRunner.java
new file mode 100644
index 0000000..36aed63
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/GraphRunner.java
@@ -0,0 +1,1023 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.os.ConditionVariable;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A GraphRunner schedules and executes the filter nodes of a graph.
+ *
+ * Typically, you create a GraphRunner given a FilterGraph instance, and execute it by calling
+ * {@link #start(FilterGraph)}.
+ *
+ * The scheduling strategy determines how the filter nodes are selected
+ * for scheduling. More precisely, given the set of nodes that can be scheduled, the scheduling
+ * strategy determines which node of this set to select for execution. For instance, an LFU
+ * scheduler (the default) chooses the node that has been executed the least amount of times.
+ */
+public final class GraphRunner {
+
+    private static int PRIORITY_SLEEP = -1;
+    private static int PRIORITY_STOP = -2;
+
+    private static final Event BEGIN_EVENT = new Event(Event.BEGIN, null);
+    private static final Event FLUSH_EVENT = new Event(Event.FLUSH, null);
+    private static final Event HALT_EVENT = new Event(Event.HALT, null);
+    private static final Event KILL_EVENT = new Event(Event.KILL, null);
+    private static final Event PAUSE_EVENT = new Event(Event.PAUSE, null);
+    private static final Event RELEASE_FRAMES_EVENT = new Event(Event.RELEASE_FRAMES, null);
+    private static final Event RESTART_EVENT = new Event(Event.RESTART, null);
+    private static final Event RESUME_EVENT = new Event(Event.RESUME, null);
+    private static final Event STEP_EVENT = new Event(Event.STEP, null);
+    private static final Event STOP_EVENT = new Event(Event.STOP, null);
+
+    private static class State {
+        public static final int STOPPED = 1;
+        public static final int PREPARING = 2;
+        public static final int RUNNING = 4;
+        public static final int PAUSED = 8;
+        public static final int HALTED = 16;
+
+        private int mCurrent = STOPPED;
+
+        public synchronized void setState(int newState) {
+            mCurrent = newState;
+        }
+
+        public synchronized boolean check(int state) {
+            return ((mCurrent & state) == state);
+        }
+
+        public synchronized boolean addState(int state) {
+            if ((mCurrent & state) != state) {
+                mCurrent |= state;
+                return true;
+            }
+            return false;
+        }
+
+        public synchronized boolean removeState(int state) {
+            boolean result = (mCurrent & state) == state;
+            mCurrent &= (~state);
+            return result;
+        }
+
+        public synchronized int current() {
+            return mCurrent;
+        }
+    }
+
+    private static class Event {
+        public static final int PREPARE = 1;
+        public static final int BEGIN = 2;
+        public static final int STEP = 3;
+        public static final int STOP = 4;
+        public static final int PAUSE = 6;
+        public static final int HALT = 7;
+        public static final int RESUME = 8;
+        public static final int RESTART = 9;
+        public static final int FLUSH = 10;
+        public static final int TEARDOWN = 11;
+        public static final int KILL = 12;
+        public static final int RELEASE_FRAMES = 13;
+
+        public int code;
+        public Object object;
+
+        public Event(int code, Object object) {
+            this.code = code;
+            this.object = object;
+        }
+    }
+
+    private final class GraphRunLoop implements Runnable {
+
+        private State mState = new State();
+        private final boolean mAllowOpenGL;
+        private RenderTarget mRenderTarget = null;
+        private LinkedBlockingQueue<Event> mEventQueue = new LinkedBlockingQueue<Event>();
+        private Exception mCaughtException = null;
+        private boolean mClosedSuccessfully = true;
+        private Stack<Filter[]> mFilters = new Stack<Filter[]>();
+        private Stack<SubListener> mSubListeners = new Stack<SubListener>();
+        private Set<FilterGraph> mOpenedGraphs = new HashSet<FilterGraph>();
+        public ConditionVariable mStopCondition = new ConditionVariable(true);
+
+        private void loop() {
+            boolean killed = false;
+            while (!killed) {
+                try {
+                    Event event = nextEvent();
+                    if (event == null) continue;
+                    switch (event.code) {
+                        case Event.PREPARE:
+                            onPrepare((FilterGraph)event.object);
+                            break;
+                        case Event.BEGIN:
+                            onBegin();
+                            break;
+                        case Event.STEP:
+                            onStep();
+                            break;
+                        case Event.STOP:
+                            onStop();
+                            break;
+                        case Event.PAUSE:
+                            onPause();
+                            break;
+                        case Event.HALT:
+                            onHalt();
+                            break;
+                        case Event.RESUME:
+                            onResume();
+                            break;
+                        case Event.RESTART:
+                            onRestart();
+                            break;
+                        case Event.FLUSH:
+                            onFlush();
+                            break;
+                        case Event.TEARDOWN:
+                            onTearDown((FilterGraph)event.object);
+                            break;
+                        case Event.KILL:
+                            killed = true;
+                            break;
+                        case Event.RELEASE_FRAMES:
+                            onReleaseFrames();
+                            break;
+                    }
+                } catch (Exception e) {
+                    if (mCaughtException == null) {
+                        mCaughtException = e;
+                        mClosedSuccessfully = true;
+                        e.printStackTrace();
+                        pushEvent(STOP_EVENT);
+                    } else {
+                        // Exception during exception recovery? Abort all processing. Do not
+                        // overwrite the original exception.
+                        mClosedSuccessfully = false;
+                        mEventQueue.clear();
+                        cleanUp();
+                    }
+                }
+            }
+        }
+
+        public GraphRunLoop(boolean allowOpenGL) {
+            mAllowOpenGL = allowOpenGL;
+        }
+
+        @Override
+        public void run() {
+            onInit();
+            loop();
+            onDestroy();
+        }
+
+        public void enterSubGraph(FilterGraph graph, SubListener listener) {
+            if (mState.check(State.RUNNING)) {
+                onOpenGraph(graph);
+                mSubListeners.push(listener);
+            }
+        }
+
+        public void pushWakeEvent(Event event) {
+            // This is of course not race-condition proof. The worst case is that the event
+            // is pushed even though the queue was not empty, which is acceptible for our cases.
+            if (mEventQueue.isEmpty()) {
+                pushEvent(event);
+            }
+        }
+
+        public void pushEvent(Event event) {
+            mEventQueue.offer(event);
+        }
+
+        public void pushEvent(int eventId, Object object) {
+            mEventQueue.offer(new Event(eventId, object));
+        }
+
+        public boolean checkState(int state) {
+            return mState.check(state);
+        }
+
+        public ConditionVariable getStopCondition() {
+            return mStopCondition;
+        }
+
+        public boolean isOpenGLAllowed() {
+            // Does not need synchronization as mAllowOpenGL flag is final.
+            return mAllowOpenGL;
+        }
+
+        private Event nextEvent() {
+            try {
+                return mEventQueue.take();
+            } catch (InterruptedException e) {
+                // Ignore and keep going.
+                Log.w("GraphRunner", "Event queue processing was interrupted.");
+                return null;
+            }
+        }
+
+        private void onPause() {
+            mState.addState(State.PAUSED);
+        }
+
+        private void onResume() {
+            if (mState.removeState(State.PAUSED)) {
+                if (mState.current() == State.RUNNING) {
+                    pushEvent(STEP_EVENT);
+                }
+            }
+        }
+
+        private void onHalt() {
+            if (mState.addState(State.HALTED) && mState.check(State.RUNNING)) {
+                closeAllFilters();
+            }
+        }
+
+        private void onRestart() {
+            if (mState.removeState(State.HALTED)) {
+                if (mState.current() == State.RUNNING) {
+                    pushEvent(STEP_EVENT);
+                }
+            }
+        }
+
+        private void onDestroy() {
+            mFrameManager.destroyBackings();
+            if (mRenderTarget != null) {
+                mRenderTarget.release();
+                mRenderTarget = null;
+            }
+        }
+
+        private void onReleaseFrames() {
+            mFrameManager.destroyBackings();
+        }
+
+        private void onInit() {
+            mThreadRunner.set(GraphRunner.this);
+            if (getContext().isOpenGLSupported()) {
+                mRenderTarget = RenderTarget.newTarget(1, 1);
+                mRenderTarget.focus();
+            }
+        }
+
+        private void onPrepare(FilterGraph graph) {
+            if (mState.current() == State.STOPPED) {
+                mState.setState(State.PREPARING);
+                mCaughtException = null;
+                onOpenGraph(graph);
+            }
+        }
+
+        private void onOpenGraph(FilterGraph graph) {
+            loadFilters(graph);
+            mOpenedGraphs.add(graph);
+            mScheduler.prepare(currentFilters());
+            pushEvent(BEGIN_EVENT);
+        }
+
+        private void onBegin() {
+            if (mState.current() == State.PREPARING) {
+                mState.setState(State.RUNNING);
+                pushEvent(STEP_EVENT);
+            }
+        }
+
+        private void onStarve() {
+            mFilters.pop();
+            if (mFilters.empty()) {
+                onStop();
+            } else {
+                SubListener listener = mSubListeners.pop();
+                if (listener != null) {
+                    listener.onSubGraphRunEnded(GraphRunner.this);
+                }
+                mScheduler.prepare(currentFilters());
+                pushEvent(STEP_EVENT);
+            }
+        }
+
+        private void onStop() {
+            if (mState.check(State.RUNNING)) {
+                // Close filters if not already halted (and already closed)
+                if (!mState.check(State.HALTED)) {
+                    closeAllFilters();
+                }
+                cleanUp();
+            }
+        }
+
+        private void cleanUp() {
+            mState.setState(State.STOPPED);
+            if (flushOnClose()) {
+                onFlush();
+            }
+            mOpenedGraphs.clear();
+            mFilters.clear();
+            onRunnerStopped(mCaughtException, mClosedSuccessfully);
+            mStopCondition.open();
+        }
+
+        private void onStep() {
+            if (mState.current() == State.RUNNING) {
+                Filter bestFilter = null;
+                long maxPriority = PRIORITY_STOP;
+                mScheduler.beginStep();
+                Filter[] filters = currentFilters();
+                for (int i = 0; i < filters.length; ++i) {
+                    Filter filter = filters[i];
+                    long priority = mScheduler.priorityForFilter(filter);
+                    if (priority > maxPriority) {
+                        maxPriority = priority;
+                        bestFilter = filter;
+                    }
+                }
+                if (maxPriority == PRIORITY_SLEEP) {
+                    // NOOP: When going into sleep mode, we simply do not schedule another node.
+                    // If some other event (such as a resume()) does schedule, then we may schedule
+                    // during sleeping. This is an edge case an irrelevant. (On the other hand,
+                    // going into a dedicated "sleep state" requires highly complex synchronization
+                    // to not "miss" a wake-up event. Thus we choose the more defensive approach
+                    // here).
+                } else if (maxPriority == PRIORITY_STOP) {
+                    onStarve();
+                } else {
+                    scheduleFilter(bestFilter);
+                    pushEvent(STEP_EVENT);
+                }
+            } else {
+                Log.w("GraphRunner", "State is not running! (" + mState.current() + ")");
+            }
+        }
+
+        private void onFlush() {
+           if (mState.check(State.HALTED) || mState.check(State.STOPPED)) {
+               for (FilterGraph graph : mOpenedGraphs) {
+                   graph.flushFrames();
+               }
+           }
+        }
+
+        private void onTearDown(FilterGraph graph) {
+            for (Filter filter : graph.getAllFilters()) {
+                filter.performTearDown();
+            }
+            graph.wipe();
+        }
+
+        private void loadFilters(FilterGraph graph) {
+            Filter[] filters = graph.getAllFilters();
+            mFilters.push(filters);
+        }
+
+        private void closeAllFilters() {
+            for (FilterGraph graph : mOpenedGraphs) {
+                closeFilters(graph);
+            }
+        }
+
+        private void closeFilters(FilterGraph graph) {
+            // [Non-iterator looping]
+            Log.v("GraphRunner", "CLOSING FILTERS");
+            Filter[] filters = graph.getAllFilters();
+            boolean isVerbose = isVerbose();
+            for (int i = 0; i < filters.length; ++i) {
+                if (isVerbose) {
+                    Log.i("GraphRunner", "Closing Filter " + filters[i] + "!");
+                }
+                filters[i].softReset();
+            }
+        }
+
+        private Filter[] currentFilters() {
+            return mFilters.peek();
+        }
+
+        private void scheduleFilter(Filter filter) {
+            long scheduleTime = 0;
+            if (isVerbose()) {
+                scheduleTime = SystemClock.elapsedRealtime();
+                Log.i("GraphRunner", scheduleTime + ": Scheduling " + filter + "!");
+            }
+            filter.execute();
+            if (isVerbose()) {
+                long nowTime = SystemClock.elapsedRealtime();
+                Log.i("GraphRunner",
+                        "-> Schedule time (" + filter + ") = " + (nowTime - scheduleTime) + " ms.");
+            }
+        }
+
+    }
+
+    // GraphRunner.Scheduler classes ///////////////////////////////////////////////////////////////
+    private interface Scheduler {
+        public void prepare(Filter[] filters);
+
+        public int getStrategy();
+
+        public void beginStep();
+
+        public long priorityForFilter(Filter filter);
+
+    }
+
+    private class LruScheduler implements Scheduler {
+
+        private long mNow;
+
+        @Override
+        public void prepare(Filter[] filters) {
+        }
+
+        @Override
+        public int getStrategy() {
+            return STRATEGY_LRU;
+        }
+
+        @Override
+        public void beginStep() {
+            // TODO(renn): We could probably do this with a simple GraphRunner counter that would
+            // represent GraphRunner local time. This would allow us to use integers instead of
+            // longs, and save us calls to the system clock.
+            mNow = SystemClock.elapsedRealtime();
+        }
+
+        @Override
+        public long priorityForFilter(Filter filter) {
+            if (filter.isSleeping()) {
+                return PRIORITY_SLEEP;
+            } else if (filter.canSchedule()) {
+                return mNow - filter.getLastScheduleTime();
+            } else {
+                return PRIORITY_STOP;
+            }
+        }
+
+    }
+
+    private class LfuScheduler implements Scheduler {
+
+        private final int MAX_PRIORITY = Integer.MAX_VALUE;
+
+        @Override
+        public void prepare(Filter[] filters) {
+            // [Non-iterator looping]
+            for (int i = 0; i < filters.length; ++i) {
+                filters[i].resetScheduleCount();
+            }
+        }
+
+        @Override
+        public int getStrategy() {
+            return STRATEGY_LFU;
+        }
+
+        @Override
+        public void beginStep() {
+        }
+
+        @Override
+        public long priorityForFilter(Filter filter) {
+            return filter.isSleeping() ? PRIORITY_SLEEP
+                    : (filter.canSchedule() ? (MAX_PRIORITY - filter.getScheduleCount())
+                            : PRIORITY_STOP);
+        }
+
+    }
+
+    private class OneShotScheduler extends LfuScheduler {
+        private int mCurCount = 1;
+
+        @Override
+        public void prepare(Filter[] filters) {
+            // [Non-iterator looping]
+            for (int i = 0; i < filters.length; ++i) {
+                filters[i].resetScheduleCount();
+            }
+        }
+
+        @Override
+        public int getStrategy() {
+            return STRATEGY_ONESHOT;
+        }
+
+        @Override
+        public void beginStep() {
+        }
+
+        @Override
+        public long priorityForFilter(Filter filter) {
+            return filter.getScheduleCount() < mCurCount ? super.priorityForFilter(filter)
+                    : PRIORITY_STOP;
+        }
+
+    }
+
+    // GraphRunner.Listener callback class /////////////////////////////////////////////////////////
+    public interface Listener {
+        /**
+         * Callback method that is called when the runner completes a run. This method is called
+         * only if the graph completed without an error.
+         */
+        public void onGraphRunnerStopped(GraphRunner runner);
+
+        /**
+         * Callback method that is called when runner encounters an error.
+         *
+         *  Any exceptions thrown in the GraphRunner's thread will cause the run to abort. The
+         * thrown exception is passed to the listener in this method. If no listener is set, the
+         * exception message is logged to the error stream. You will not receive an
+         * {@link #onGraphRunnerStopped(GraphRunner)} callback in case of an error.
+         *
+         * @param exception the exception that was thrown.
+         * @param closedSuccessfully true, if the graph was closed successfully after the error.
+         */
+        public void onGraphRunnerError(Exception exception, boolean closedSuccessfully);
+    }
+
+    public interface SubListener {
+        public void onSubGraphRunEnded(GraphRunner runner);
+    }
+
+    /**
+     * Config class to setup a GraphRunner with a custom configuration.
+     *
+     * The configuration object is passed to the constructor. Any changes to it will not affect
+     * the created GraphRunner instance.
+     */
+    public static class Config {
+        /** The runner's thread priority. */
+        public int threadPriority = Thread.NORM_PRIORITY;
+        /** Whether to allow filters to use OpenGL or not. */
+        public boolean allowOpenGL = true;
+    }
+
+    /** Parameters shared between run-thread and GraphRunner frontend. */
+    private class RunParameters {
+        public Listener listener = null;
+        public boolean isVerbose = false;
+        public boolean flushOnClose = true;
+    }
+
+    // GraphRunner implementation //////////////////////////////////////////////////////////////////
+    /** Schedule strategy: From set of candidates, pick a random one. */
+    public static final int STRATEGY_RANDOM = 1;
+    /** Schedule strategy: From set of candidates, pick node executed least recently executed. */
+    public static final int STRATEGY_LRU = 2;
+    /** Schedule strategy: From set of candidates, pick node executed least number of times. */
+    public static final int STRATEGY_LFU = 3;
+    /** Schedule strategy: Schedules no node more than once. */
+    public static final int STRATEGY_ONESHOT = 4;
+
+    private final MffContext mContext;
+
+    private FilterGraph mRunningGraph = null;
+    private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
+
+    private Scheduler mScheduler;
+
+    private GraphRunLoop mRunLoop;
+
+    private Thread mRunThread = null;
+
+    private FrameManager mFrameManager = null;
+
+    private static ThreadLocal<GraphRunner> mThreadRunner = new ThreadLocal<GraphRunner>();
+
+    private RunParameters mParams = new RunParameters();
+
+    /**
+     * Creates a new GraphRunner with the default configuration. You must attach FilterGraph
+     * instances to this runner before you can execute any of these graphs.
+     *
+     * @param context The MffContext instance for this runner.
+     */
+    public GraphRunner(MffContext context) {
+        mContext = context;
+        init(new Config());
+    }
+
+    /**
+     * Creates a new GraphRunner with the specified configuration. You must attach FilterGraph
+     * instances to this runner before you can execute any of these graphs.
+     *
+     * @param context The MffContext instance for this runner.
+     * @param config A Config instance with the configuration of this runner.
+     */
+    public GraphRunner(MffContext context, Config config) {
+        mContext = context;
+        init(config);
+    }
+
+    /**
+     * Returns the currently running graph-runner.
+     * @return The currently running graph-runner.
+     */
+    public static GraphRunner current() {
+        return mThreadRunner.get();
+    }
+
+    /**
+     * Returns the graph that this runner is currently executing. Returns null if no graph is
+     * currently being executed by this runner.
+     *
+     * @return the FilterGraph instance that this GraphRunner is executing.
+     */
+    public synchronized FilterGraph getRunningGraph() {
+        return mRunningGraph;
+    }
+
+    /**
+     * Returns the context that this runner is bound to.
+     *
+     * @return the MffContext instance that this runner is bound to.
+     */
+    public MffContext getContext() {
+        return mContext;
+    }
+
+    /**
+     * Begins graph execution. The graph filters are scheduled and executed until processing
+     * finishes or is stopped.
+     */
+    public synchronized void start(FilterGraph graph) {
+        if (graph.mRunner != this) {
+            throw new IllegalArgumentException("Graph must be attached to runner!");
+        }
+        mRunningGraph = graph;
+        mRunLoop.getStopCondition().close();
+        mRunLoop.pushEvent(Event.PREPARE, graph);
+    }
+
+    /**
+     * Begin executing a sub-graph. This only succeeds if the current runner is already
+     * executing.
+     */
+    public void enterSubGraph(FilterGraph graph, SubListener listener) {
+        if (Thread.currentThread() != mRunThread) {
+            throw new RuntimeException("enterSubGraph must be called from the runner's thread!");
+        }
+        mRunLoop.enterSubGraph(graph, listener);
+    }
+
+    /**
+     * Waits until graph execution has finished or stopped with an error.
+     * Care must be taken when using this method to not block the UI thread. This is typically
+     * used when a graph is run in one-shot mode to compute a result.
+     */
+    public void waitUntilStop() {
+        mRunLoop.getStopCondition().block();
+    }
+
+    /**
+     * Pauses graph execution.
+     */
+    public void pause() {
+        mRunLoop.pushEvent(PAUSE_EVENT);
+    }
+
+    /**
+     * Resumes graph execution after pausing.
+     */
+    public void resume() {
+        mRunLoop.pushEvent(RESUME_EVENT);
+    }
+
+    /**
+     * Stops graph execution.
+     */
+    public void stop() {
+        mRunLoop.pushEvent(STOP_EVENT);
+    }
+
+    /**
+     * Returns whether the graph is currently being executed. A graph is considered to be running,
+     * even if it is paused or in the process of being stopped.
+     *
+     * @return true, if the graph is currently being executed.
+     */
+    public boolean isRunning() {
+        return !mRunLoop.checkState(State.STOPPED);
+    }
+
+    /**
+     * Returns whether the graph is currently paused.
+     *
+     * @return true, if the graph is currently paused.
+     */
+    public boolean isPaused() {
+        return mRunLoop.checkState(State.PAUSED);
+    }
+
+    /**
+     * Returns whether the graph is currently stopped.
+     *
+     * @return true, if the graph is currently stopped.
+     */
+    public boolean isStopped() {
+        return mRunLoop.checkState(State.STOPPED);
+    }
+
+    /**
+     * Sets the filter scheduling strategy. This method can not be called when the GraphRunner is
+     * running.
+     *
+     * @param strategy a constant specifying which scheduler strategy to use.
+     * @throws RuntimeException if the GraphRunner is running.
+     * @throws IllegalArgumentException if invalid strategy is specified.
+     * @see #getSchedulerStrategy()
+     */
+    public void setSchedulerStrategy(int strategy) {
+        if (isRunning()) {
+            throw new RuntimeException(
+                    "Attempting to change scheduling strategy on running " + "GraphRunner!");
+        }
+        createScheduler(strategy);
+    }
+
+    /**
+     * Returns the current scheduling strategy.
+     *
+     * @return the scheduling strategy used by this GraphRunner.
+     * @see #setSchedulerStrategy(int)
+     */
+    public int getSchedulerStrategy() {
+        return mScheduler.getStrategy();
+    }
+
+    /**
+     * Set whether or not the runner is verbose. When set to true, the runner will output individual
+     * scheduling steps that may help identify and debug problems in the graph structure. The
+     * default is false.
+     *
+     * @param isVerbose true, if the GraphRunner should log scheduling details.
+     * @see #isVerbose()
+     */
+    public void setIsVerbose(boolean isVerbose) {
+        synchronized (mParams) {
+            mParams.isVerbose = isVerbose;
+        }
+    }
+
+    /**
+     * Returns whether the GraphRunner is verbose.
+     *
+     * @return true, if the GraphRunner logs scheduling details.
+     * @see #setIsVerbose(boolean)
+     */
+    public boolean isVerbose() {
+        synchronized (mParams) {
+            return mParams.isVerbose;
+        }
+    }
+
+    /**
+     * Returns whether Filters of this GraphRunner can use OpenGL.
+     *
+     * Filters may use OpenGL if the MffContext supports OpenGL and the GraphRunner allows it.
+     *
+     * @return true, if Filters are allowed to use OpenGL.
+     */
+    public boolean isOpenGLSupported() {
+        return mRunLoop.isOpenGLAllowed() && mContext.isOpenGLSupported();
+    }
+
+    /**
+     * Enable flushing all frames from the graph when running completes.
+     *
+     * If this is set to false, then frames may remain in the pipeline even after running completes.
+     * The default value is true.
+     *
+     * @param flush true, if the GraphRunner should flush the graph when running completes.
+     * @see #flushOnClose()
+     */
+    public void setFlushOnClose(boolean flush) {
+        synchronized (mParams) {
+            mParams.flushOnClose = flush;
+        }
+    }
+
+    /**
+     * Returns whether the GraphRunner flushes frames when running completes.
+     *
+     * @return true, if the GraphRunner flushes frames when running completes.
+     * @see #setFlushOnClose(boolean)
+     */
+    public boolean flushOnClose() {
+        synchronized (mParams) {
+            return mParams.flushOnClose;
+        }
+    }
+
+    /**
+     * Sets the listener for receiving runtime events. A GraphRunner.Listener instance can be used
+     * to determine when certain events occur during graph execution (and react on them). See the
+     * {@link GraphRunner.Listener} class for details.
+     *
+     * @param listener the GraphRunner.Listener instance to set.
+     * @see #getListener()
+     */
+    public void setListener(Listener listener) {
+        synchronized (mParams) {
+            mParams.listener = listener;
+        }
+    }
+
+    /**
+     * Returns the currently assigned GraphRunner.Listener.
+     *
+     * @return the currently assigned GraphRunner.Listener instance.
+     * @see #setListener(Listener)
+     */
+    public Listener getListener() {
+        synchronized (mParams) {
+            return mParams.listener;
+        }
+    }
+
+    /**
+     * Returns the FrameManager that manages the runner's frames.
+     *
+     * @return the FrameManager instance that manages the runner's frames.
+     */
+    public FrameManager getFrameManager() {
+        return mFrameManager;
+    }
+
+    /**
+     * Tear down a GraphRunner and all its resources.
+     * <p>
+     * You must make sure that before calling this, no more graphs are attached to this runner.
+     * Typically, graphs are removed from runners when they are torn down.
+     *
+     * @throws IllegalStateException if there are still graphs attached to this runner.
+     */
+    public void tearDown() {
+        synchronized (mGraphs) {
+            if (!mGraphs.isEmpty()) {
+                throw new IllegalStateException("Attempting to tear down runner with "
+                        + mGraphs.size() + " graphs still attached!");
+            }
+        }
+        mRunLoop.pushEvent(KILL_EVENT);
+        // Wait for thread to complete, so that everything is torn down by the time we return.
+        try {
+            mRunThread.join();
+        } catch (InterruptedException e) {
+            Log.e("GraphRunner", "Error waiting for runner thread to finish!");
+        }
+    }
+
+    /**
+     * Release all frames managed by this runner.
+     * <p>
+     * Note, that you must make sure no graphs are attached to this runner before calling this
+     * method, as otherwise Filters in the graph may reference frames that are now released.
+     *
+     * TODO: Eventually, this method should be removed. Instead we should have better analysis
+     * that catches leaking frames from filters.
+     *
+     * @throws IllegalStateException if there are still graphs attached to this runner.
+     */
+    public void releaseFrames() {
+        synchronized (mGraphs) {
+            if (!mGraphs.isEmpty()) {
+                throw new IllegalStateException("Attempting to release frames with "
+                        + mGraphs.size() + " graphs still attached!");
+            }
+        }
+        mRunLoop.pushEvent(RELEASE_FRAMES_EVENT);
+    }
+
+    // Core internal methods ///////////////////////////////////////////////////////////////////////
+    void attachGraph(FilterGraph graph) {
+        synchronized (mGraphs) {
+            mGraphs.add(graph);
+        }
+    }
+
+    void signalWakeUp() {
+        mRunLoop.pushWakeEvent(STEP_EVENT);
+    }
+
+    void begin() {
+        mRunLoop.pushEvent(BEGIN_EVENT);
+    }
+
+    /** Like pause(), but closes all filters. Can be resumed using restart(). */
+    void halt() {
+        mRunLoop.pushEvent(HALT_EVENT);
+    }
+
+    /** Resumes a previously halted runner, and restores it to its non-halted state. */
+    void restart() {
+        mRunLoop.pushEvent(RESTART_EVENT);
+    }
+
+    /**
+     * Tears down the specified graph.
+     *
+     * The graph must be attached to this runner.
+     */
+    void tearDownGraph(FilterGraph graph) {
+        if (graph.getRunner() != this) {
+            throw new IllegalArgumentException("Attempting to tear down graph with foreign "
+                    + "GraphRunner!");
+        }
+        mRunLoop.pushEvent(Event.TEARDOWN, graph);
+        synchronized (mGraphs) {
+            mGraphs.remove(graph);
+        }
+    }
+
+    /**
+     * Remove all frames that are waiting to be processed.
+     *
+     * Removes and releases frames that are waiting in the graph connections of the currently
+     * halted graphs, i.e. frames that are waiting to be processed. This does not include frames
+     * that may be held or cached by filters themselves.
+     *
+     * TODO: With the new sub-graph architecture, this can now be simplified and made public.
+     * It can then no longer rely on opened graphs, and instead flush a graph and all its
+     * sub-graphs.
+     */
+    void flushFrames() {
+        mRunLoop.pushEvent(FLUSH_EVENT);
+    }
+
+    // Private methods /////////////////////////////////////////////////////////////////////////////
+    private void init(Config config) {
+        mFrameManager = new FrameManager(this, FrameManager.FRAME_CACHE_LRU);
+        createScheduler(STRATEGY_LRU);
+        mRunLoop = new GraphRunLoop(config.allowOpenGL);
+        mRunThread = new Thread(mRunLoop);
+        mRunThread.setPriority(config.threadPriority);
+        mRunThread.start();
+        mContext.addRunner(this);
+    }
+
+    private void createScheduler(int strategy) {
+        switch (strategy) {
+            case STRATEGY_LRU:
+                mScheduler = new LruScheduler();
+                break;
+            case STRATEGY_LFU:
+                mScheduler = new LfuScheduler();
+                break;
+            case STRATEGY_ONESHOT:
+                mScheduler = new OneShotScheduler();
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown schedule-strategy constant " + strategy + "!");
+        }
+    }
+
+    // Called within the runner's thread
+    private void onRunnerStopped(final Exception exception, final boolean closed) {
+        mRunningGraph = null;
+        synchronized (mParams) {
+            if (mParams.listener != null) {
+                getContext().postRunnable(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (exception == null) {
+                            mParams.listener.onGraphRunnerStopped(GraphRunner.this);
+                        } else {
+                            mParams.listener.onGraphRunnerError(exception, closed);
+                        }
+                    }
+                });
+            } else if (exception != null) {
+                Log.e("GraphRunner",
+                        "Uncaught exception during graph execution! Stack Trace: ");
+                exception.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ImageShader.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ImageShader.java
new file mode 100644
index 0000000..0ec50a3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ImageShader.java
@@ -0,0 +1,793 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import androidx.media.filterfw.geometry.Quad;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Convenience class to perform GL shader operations on image data.
+ * <p>
+ * The ImageShader class greatly simplifies the task of running GL shader language kernels over
+ * Frame data buffers that contain RGBA image data.
+ * </p><p>
+ * TODO: More documentation
+ * </p>
+ */
+public class ImageShader {
+
+    private int mProgram = 0;
+    private boolean mClearsOutput = false;
+    private float[] mClearColor = { 0f, 0f, 0f, 0f };
+    private boolean mBlendEnabled = false;
+    private int mSFactor = GLES20.GL_SRC_ALPHA;
+    private int mDFactor = GLES20.GL_ONE_MINUS_SRC_ALPHA;
+    private int mDrawMode = GLES20.GL_TRIANGLE_STRIP;
+    private int mVertexCount = 4;
+    private int mBaseTexUnit = GLES20.GL_TEXTURE0;
+    private int mClearBuffers = GLES20.GL_COLOR_BUFFER_BIT;
+    private float[] mSourceCoords = new float[] { 0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f };
+    private float[] mTargetCoords = new float[] { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f };
+
+    private HashMap<String, ProgramUniform> mUniforms;
+    private HashMap<String, VertexAttribute> mAttributes = new HashMap<String, VertexAttribute>();
+
+    private final static int FLOAT_SIZE = 4;
+
+    private final static String mDefaultVertexShader =
+        "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";
+
+    private final static String mIdentityShader =
+        "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";
+
+    private static class VertexAttribute {
+        private String mName;
+        private boolean mIsConst;
+        private int mIndex;
+        private boolean mShouldNormalize;
+        private int mOffset;
+        private int mStride;
+        private int mComponents;
+        private int mType;
+        private int mVbo;
+        private int mLength;
+        private FloatBuffer mValues;
+
+        public VertexAttribute(String name, int index) {
+            mName = name;
+            mIndex = index;
+            mLength = -1;
+        }
+
+        public void set(boolean normalize, int stride, int components, int type, float[] values) {
+            mIsConst = false;
+            mShouldNormalize = normalize;
+            mStride = stride;
+            mComponents = components;
+            mType = type;
+            mVbo = 0;
+            if (mLength != values.length){
+                initBuffer(values);
+                mLength = values.length;
+            }
+            copyValues(values);
+        }
+
+        public void set(boolean normalize, int offset, int stride, int components, int type,
+                int vbo){
+            mIsConst = false;
+            mShouldNormalize = normalize;
+            mOffset = offset;
+            mStride = stride;
+            mComponents = components;
+            mType = type;
+            mVbo = vbo;
+            mValues = null;
+        }
+
+        public boolean push() {
+            if (mIsConst) {
+                switch (mComponents) {
+                    case 1:
+                        GLES20.glVertexAttrib1fv(mIndex, mValues);
+                        break;
+                    case 2:
+                        GLES20.glVertexAttrib2fv(mIndex, mValues);
+                        break;
+                    case 3:
+                        GLES20.glVertexAttrib3fv(mIndex, mValues);
+                        break;
+                    case 4:
+                        GLES20.glVertexAttrib4fv(mIndex, mValues);
+                        break;
+                    default:
+                        return false;
+                }
+                GLES20.glDisableVertexAttribArray(mIndex);
+            } else {
+                if (mValues != null) {
+                    // Note that we cannot do any size checking here, as the correct component
+                    // count depends on the drawing step. GL should catch such errors then, and
+                    // we will report them to the user.
+                    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+                    GLES20.glVertexAttribPointer(mIndex,
+                                                 mComponents,
+                                                 mType,
+                                                 mShouldNormalize,
+                                                 mStride,
+                                                 mValues);
+                } else {
+                    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo);
+                    GLES20.glVertexAttribPointer(mIndex,
+                                                 mComponents,
+                                                 mType,
+                                                 mShouldNormalize,
+                                                 mStride,
+                                                 mOffset);
+                }
+                GLES20.glEnableVertexAttribArray(mIndex);
+            }
+            GLToolbox.checkGlError("Set vertex-attribute values");
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+
+        private void initBuffer(float[] values) {
+            mValues = ByteBuffer.allocateDirect(values.length * FLOAT_SIZE)
+                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+        }
+
+        private void copyValues(float[] values) {
+            mValues.put(values).position(0);
+        }
+
+    }
+
+    private static final class ProgramUniform {
+        private String mName;
+        private int mLocation;
+        private int mType;
+        private int mSize;
+
+        public ProgramUniform(int program, int index) {
+            int[] len = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0);
+
+            int[] type = new int[1];
+            int[] size = new int[1];
+            byte[] name = new byte[len[0]];
+            int[] ignore = new int[1];
+
+            GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0);
+            mName = new String(name, 0, strlen(name));
+            mLocation = GLES20.glGetUniformLocation(program, mName);
+            mType = type[0];
+            mSize = size[0];
+            GLToolbox.checkGlError("Initializing uniform");
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        public int getLocation() {
+            return mLocation;
+        }
+
+        public int getSize() {
+            return mSize;
+        }
+    }
+
+    public ImageShader(String fragmentShader) {
+        mProgram = createProgram(mDefaultVertexShader, fragmentShader);
+        scanUniforms();
+    }
+
+    public ImageShader(String vertexShader, String fragmentShader) {
+        mProgram = createProgram(vertexShader, fragmentShader);
+        scanUniforms();
+    }
+
+    public static ImageShader createIdentity() {
+        return new ImageShader(mIdentityShader);
+    }
+
+    public static ImageShader createIdentity(String vertexShader) {
+        return new ImageShader(vertexShader, mIdentityShader);
+    }
+
+    public static void renderTextureToTarget(TextureSource texture,
+                                             RenderTarget target,
+                                             int width,
+                                             int height) {
+        ImageShader shader = RenderTarget.currentTarget().getIdentityShader();
+        shader.process(texture, target, width, height);
+    }
+
+    public void process(FrameImage2D input, FrameImage2D output) {
+        TextureSource texSource = input.lockTextureSource();
+        RenderTarget renderTarget = output.lockRenderTarget();
+        processMulti(new TextureSource[] { texSource },
+                     renderTarget,
+                     output.getWidth(),
+                     output.getHeight());
+        input.unlock();
+        output.unlock();
+    }
+
+    public void processMulti(FrameImage2D[] inputs, FrameImage2D output) {
+        TextureSource[] texSources = new TextureSource[inputs.length];
+        for (int i = 0; i < inputs.length; ++i) {
+            texSources[i] = inputs[i].lockTextureSource();
+        }
+        RenderTarget renderTarget = output.lockRenderTarget();
+        processMulti(texSources,
+                     renderTarget,
+                     output.getWidth(),
+                     output.getHeight());
+        for (FrameImage2D input : inputs) {
+            input.unlock();
+        }
+        output.unlock();
+    }
+
+    public void process(TextureSource texture, RenderTarget target, int width, int height) {
+        processMulti(new TextureSource[] { texture }, target, width, height);
+    }
+
+    public void processMulti(TextureSource[] sources, RenderTarget target, int width, int height) {
+        GLToolbox.checkGlError("Unknown Operation");
+        checkExecutable();
+        checkTexCount(sources.length);
+        focusTarget(target, width, height);
+        pushShaderState();
+        bindInputTextures(sources);
+        render();
+    }
+
+    public void processNoInput(FrameImage2D output) {
+        RenderTarget renderTarget = output.lockRenderTarget();
+        processNoInput(renderTarget, output.getWidth(), output.getHeight());
+        output.unlock();
+    }
+
+    public void processNoInput(RenderTarget target, int width, int height) {
+        processMulti(new TextureSource[] {}, target, width, height);
+    }
+
+    public int getUniformLocation(String name) {
+        return getProgramUniform(name, true).getLocation();
+    }
+
+    public int getAttributeLocation(String name) {
+        if (name.equals(positionAttributeName()) || name.equals(texCoordAttributeName())) {
+            Log.w("ImageShader", "Attempting to access internal attribute '" + name
+                + "' directly!");
+        }
+        int loc = GLES20.glGetAttribLocation(mProgram, name);
+        if (loc < 0) {
+            throw new RuntimeException("Unknown attribute '" + name + "' in shader program!");
+        }
+        return loc;
+    }
+
+    public void setUniformValue(String uniformName, int value) {
+        useProgram();
+        int uniformHandle = getUniformLocation(uniformName);
+        GLES20.glUniform1i(uniformHandle, value);
+        GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
+    }
+
+    public void setUniformValue(String uniformName, float value) {
+        useProgram();
+        int uniformHandle = getUniformLocation(uniformName);
+        GLES20.glUniform1f(uniformHandle, value);
+        GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
+    }
+
+    public void setUniformValue(String uniformName, int[] values) {
+        ProgramUniform uniform = getProgramUniform(uniformName, true);
+        useProgram();
+        int len = values.length;
+        switch (uniform.getType()) {
+            case GLES20.GL_INT:
+                checkUniformAssignment(uniform, len, 1);
+                GLES20.glUniform1iv(uniform.getLocation(), len, values, 0);
+                break;
+            case GLES20.GL_INT_VEC2:
+                checkUniformAssignment(uniform, len, 2);
+                GLES20.glUniform2iv(uniform.getLocation(), len / 2, values, 0);
+                break;
+            case GLES20.GL_INT_VEC3:
+                checkUniformAssignment(uniform, len, 3);
+                GLES20.glUniform2iv(uniform.getLocation(), len / 3, values, 0);
+                break;
+            case GLES20.GL_INT_VEC4:
+                checkUniformAssignment(uniform, len, 4);
+                GLES20.glUniform2iv(uniform.getLocation(), len / 4, values, 0);
+                break;
+            default:
+                throw new RuntimeException("Cannot assign int-array to incompatible uniform type "
+                    + "for uniform '" + uniformName + "'!");
+        }
+        GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
+    }
+
+
+    public void setUniformValue(String uniformName, float[] values) {
+        ProgramUniform uniform = getProgramUniform(uniformName, true);
+        useProgram();
+        int len = values.length;
+        switch (uniform.getType()) {
+            case GLES20.GL_FLOAT:
+                checkUniformAssignment(uniform, len, 1);
+                GLES20.glUniform1fv(uniform.getLocation(), len, values, 0);
+                break;
+            case GLES20.GL_FLOAT_VEC2:
+                checkUniformAssignment(uniform, len, 2);
+                GLES20.glUniform2fv(uniform.getLocation(), len / 2, values, 0);
+                break;
+            case GLES20.GL_FLOAT_VEC3:
+                checkUniformAssignment(uniform, len, 3);
+                GLES20.glUniform3fv(uniform.getLocation(), len / 3, values, 0);
+                break;
+            case GLES20.GL_FLOAT_VEC4:
+                checkUniformAssignment(uniform, len, 4);
+                GLES20.glUniform4fv(uniform.getLocation(), len / 4, values, 0);
+                break;
+            case GLES20.GL_FLOAT_MAT2:
+                checkUniformAssignment(uniform, len, 4);
+                GLES20.glUniformMatrix2fv(uniform.getLocation(), len / 4, false, values, 0);
+                break;
+            case GLES20.GL_FLOAT_MAT3:
+                checkUniformAssignment(uniform, len, 9);
+                GLES20.glUniformMatrix3fv(uniform.getLocation(), len / 9, false, values, 0);
+                break;
+            case GLES20.GL_FLOAT_MAT4:
+                checkUniformAssignment(uniform, len, 16);
+                GLES20.glUniformMatrix4fv(uniform.getLocation(), len / 16, false, values, 0);
+                break;
+            default:
+                throw new RuntimeException("Cannot assign float-array to incompatible uniform type "
+                    + "for uniform '" + uniformName + "'!");
+        }
+        GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
+    }
+
+    public void setAttributeValues(String attributeName, float[] data, int components) {
+        VertexAttribute attr = getProgramAttribute(attributeName, true);
+        attr.set(false, FLOAT_SIZE * components, components, GLES20.GL_FLOAT, data);
+    }
+
+    public void setAttributeValues(String attributeName, int vbo, int type, int components,
+                                   int stride, int offset, boolean normalize) {
+        VertexAttribute attr = getProgramAttribute(attributeName, true);
+        attr.set(normalize, offset, stride, components, type, vbo);
+    }
+
+    public void setSourceRect(float x, float y, float width, float height) {
+        setSourceCoords(new float[] { x, y, x + width, y, x, y + height, x + width, y + height });
+    }
+
+    public void setSourceRect(RectF rect) {
+        setSourceRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+    }
+
+    public void setSourceQuad(Quad quad) {
+        setSourceCoords(new float[] { quad.topLeft().x,     quad.topLeft().y,
+                                      quad.topRight().x,    quad.topRight().y,
+                                      quad.bottomLeft().x,  quad.bottomLeft().y,
+                                      quad.bottomRight().x, quad.bottomRight().y });
+    }
+
+    public void setSourceCoords(float[] coords) {
+        if (coords.length != 8) {
+            throw new IllegalArgumentException("Expected 8 coordinates as source coordinates but "
+                + "got " + coords.length + " coordinates!");
+        }
+        mSourceCoords = Arrays.copyOf(coords, 8);
+    }
+
+    public void setSourceTransform(float[] matrix) {
+        if (matrix.length != 16) {
+            throw new IllegalArgumentException("Expected 4x4 matrix for source transform!");
+        }
+        setSourceCoords(new float[] {
+            matrix[12],
+            matrix[13],
+
+            matrix[0] + matrix[12],
+            matrix[1] + matrix[13],
+
+            matrix[4] + matrix[12],
+            matrix[5] + matrix[13],
+
+            matrix[0] + matrix[4] + matrix[12],
+            matrix[1] + matrix[5] + matrix[13],
+        });
+    }
+
+    public void setTargetRect(float x, float y, float width, float height) {
+        setTargetCoords(new float[] { x, y,
+                                      x + width, y,
+                                      x, y + height,
+                                      x + width, y + height });
+    }
+
+    public void setTargetRect(RectF rect) {
+        setTargetCoords(new float[] { rect.left,    rect.top,
+                                      rect.right,   rect.top,
+                                      rect.left,    rect.bottom,
+                                      rect.right,   rect.bottom });
+    }
+
+    public void setTargetQuad(Quad quad) {
+        setTargetCoords(new float[] { quad.topLeft().x,     quad.topLeft().y,
+                                      quad.topRight().x,    quad.topRight().y,
+                                      quad.bottomLeft().x,  quad.bottomLeft().y,
+                                      quad.bottomRight().x, quad.bottomRight().y });
+    }
+
+    public void setTargetCoords(float[] coords) {
+        if (coords.length != 8) {
+            throw new IllegalArgumentException("Expected 8 coordinates as target coordinates but "
+                + "got " + coords.length + " coordinates!");
+        }
+        mTargetCoords = new float[8];
+        for (int i = 0; i < 8; ++i) {
+            mTargetCoords[i] = coords[i] * 2f - 1f;
+        }
+    }
+
+    public void setTargetTransform(float[] matrix) {
+        if (matrix.length != 16) {
+            throw new IllegalArgumentException("Expected 4x4 matrix for target transform!");
+        }
+        setTargetCoords(new float[] {
+            matrix[12],
+            matrix[13],
+
+            matrix[0] + matrix[12],
+            matrix[1] + matrix[13],
+
+            matrix[4] + matrix[12],
+            matrix[5] + matrix[13],
+
+            matrix[0] + matrix[4] + matrix[12],
+            matrix[1] + matrix[5] + matrix[13],
+        });
+    }
+
+    public void setClearsOutput(boolean clears) {
+        mClearsOutput = clears;
+    }
+
+    public boolean getClearsOutput() {
+        return mClearsOutput;
+    }
+
+    public void setClearColor(float[] rgba) {
+        mClearColor = rgba;
+    }
+
+    public float[] getClearColor() {
+        return mClearColor;
+    }
+
+    public void setClearBufferMask(int bufferMask) {
+        mClearBuffers = bufferMask;
+    }
+
+    public int getClearBufferMask() {
+        return mClearBuffers;
+    }
+
+    public void setBlendEnabled(boolean enable) {
+        mBlendEnabled = enable;
+    }
+
+    public boolean getBlendEnabled() {
+        return mBlendEnabled;
+    }
+
+    public void setBlendFunc(int sFactor, int dFactor) {
+        mSFactor = sFactor;
+        mDFactor = dFactor;
+    }
+
+    public void setDrawMode(int drawMode) {
+        mDrawMode = drawMode;
+    }
+
+    public int getDrawMode() {
+        return mDrawMode;
+    }
+
+    public void setVertexCount(int count) {
+        mVertexCount = count;
+    }
+
+    public int getVertexCount() {
+        return mVertexCount;
+    }
+
+    public void setBaseTextureUnit(int baseTexUnit) {
+        mBaseTexUnit = baseTexUnit;
+    }
+
+    public int baseTextureUnit() {
+        return mBaseTexUnit;
+    }
+
+    public String texCoordAttributeName() {
+        return "a_texcoord";
+    }
+
+    public String positionAttributeName() {
+        return "a_position";
+    }
+
+    public String inputTextureUniformName(int index) {
+        return "tex_sampler_" + index;
+    }
+
+    public static int maxTextureUnits() {
+        return GLES20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        GLES20.glDeleteProgram(mProgram);
+    }
+
+    protected void pushShaderState() {
+        useProgram();
+        updateSourceCoordAttribute();
+        updateTargetCoordAttribute();
+        pushAttributes();
+        if (mClearsOutput) {
+            GLES20.glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], mClearColor[3]);
+            GLES20.glClear(mClearBuffers);
+        }
+        if (mBlendEnabled) {
+            GLES20.glEnable(GLES20.GL_BLEND);
+            GLES20.glBlendFunc(mSFactor, mDFactor);
+        } else {
+            GLES20.glDisable(GLES20.GL_BLEND);
+        }
+        GLToolbox.checkGlError("Set render variables");
+    }
+
+    private void focusTarget(RenderTarget target, int width, int height) {
+        target.focus();
+        GLES20.glViewport(0, 0, width, height);
+        GLToolbox.checkGlError("glViewport");
+    }
+
+    private void bindInputTextures(TextureSource[] sources) {
+        for (int i = 0; i < sources.length; ++i) {
+            // Activate texture unit i
+            GLES20.glActiveTexture(baseTextureUnit() + i);
+
+            // Bind texture
+            sources[i].bind();
+
+            // Assign the texture uniform in the shader to unit i
+            int texUniform = GLES20.glGetUniformLocation(mProgram, inputTextureUniformName(i));
+            if (texUniform >= 0) {
+                GLES20.glUniform1i(texUniform, i);
+            } else {
+                throw new RuntimeException("Shader does not seem to support " + sources.length
+                    + " number of input textures! Missing uniform " + inputTextureUniformName(i)
+                    + "!");
+            }
+            GLToolbox.checkGlError("Binding input texture " + i);
+        }
+    }
+
+    private void pushAttributes() {
+        for (VertexAttribute attr : mAttributes.values()) {
+            if (!attr.push()) {
+                throw new RuntimeException("Unable to assign attribute value '" + attr + "'!");
+            }
+        }
+        GLToolbox.checkGlError("Push Attributes");
+    }
+
+    private void updateSourceCoordAttribute() {
+        // If attribute does not exist, simply do nothing (may be custom shader).
+        VertexAttribute attr = getProgramAttribute(texCoordAttributeName(), false);
+        // A non-null value of mSourceCoords indicates new values to be set.
+        if (mSourceCoords != null && attr != null) {
+            // Upload new source coordinates to GPU
+            attr.set(false, FLOAT_SIZE * 2, 2, GLES20.GL_FLOAT, mSourceCoords);
+        }
+        // Do not set again (even if failed, to not cause endless attempts)
+        mSourceCoords = null;
+    }
+
+    private void updateTargetCoordAttribute() {
+        // If attribute does not exist, simply do nothing (may be custom shader).
+        VertexAttribute attr = getProgramAttribute(positionAttributeName(), false);
+        // A non-null value of mTargetCoords indicates new values to be set.
+        if (mTargetCoords != null && attr != null) {
+            // Upload new target coordinates to GPU
+            attr.set(false, FLOAT_SIZE * 2, 2, GLES20.GL_FLOAT, mTargetCoords);
+        }
+        // Do not set again (even if failed, to not cause endless attempts)
+        mTargetCoords = null;
+    }
+
+    private void render() {
+        GLES20.glDrawArrays(mDrawMode, 0, mVertexCount);
+        GLToolbox.checkGlError("glDrawArrays");
+    }
+
+    private void checkExecutable() {
+        if (mProgram == 0) {
+            throw new RuntimeException("Attempting to execute invalid shader-program!");
+        }
+    }
+
+    private void useProgram() {
+        GLES20.glUseProgram(mProgram);
+        GLToolbox.checkGlError("glUseProgram");
+    }
+
+    private static void checkTexCount(int count) {
+        if (count > maxTextureUnits()) {
+            throw new RuntimeException("Number of textures passed (" + count + ") exceeds the "
+                + "maximum number of allowed texture units (" + maxTextureUnits() + ")!");
+        }
+    }
+
+    private static int loadShader(int shaderType, String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        if (shader != 0) {
+            GLES20.glShaderSource(shader, source);
+            GLES20.glCompileShader(shader);
+            int[] compiled = new int[1];
+            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+            if (compiled[0] == 0) {
+                String info = GLES20.glGetShaderInfoLog(shader);
+                GLES20.glDeleteShader(shader);
+                shader = 0;
+                throw new RuntimeException("Could not compile shader " + shaderType + ":" + info);
+            }
+        }
+        return shader;
+    }
+
+    private static int createProgram(String vertexSource, String fragmentSource) {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+        if (vertexShader == 0) {
+            throw new RuntimeException("Could not create shader-program as vertex shader "
+                + "could not be compiled!");
+        }
+        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+        if (pixelShader == 0) {
+            throw new RuntimeException("Could not create shader-program as fragment shader "
+                + "could not be compiled!");
+        }
+
+        int program = GLES20.glCreateProgram();
+        if (program != 0) {
+            GLES20.glAttachShader(program, vertexShader);
+            GLToolbox.checkGlError("glAttachShader");
+            GLES20.glAttachShader(program, pixelShader);
+            GLToolbox.checkGlError("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                String info = GLES20.glGetProgramInfoLog(program);
+                GLES20.glDeleteProgram(program);
+                program = 0;
+                throw new RuntimeException("Could not link program: " + info);
+            }
+        }
+
+        GLES20.glDeleteShader(vertexShader);
+        GLES20.glDeleteShader(pixelShader);
+
+        return program;
+    }
+
+    private void scanUniforms() {
+        int uniformCount[] = new int [1];
+        GLES20.glGetProgramiv(mProgram, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0);
+        if (uniformCount[0] > 0) {
+            mUniforms = new HashMap<String, ProgramUniform>(uniformCount[0]);
+            for (int i = 0; i < uniformCount[0]; ++i) {
+                ProgramUniform uniform = new ProgramUniform(mProgram, i);
+                mUniforms.put(uniform.getName(), uniform);
+            }
+        }
+    }
+
+    private ProgramUniform getProgramUniform(String name, boolean required) {
+        ProgramUniform result = mUniforms.get(name);
+        if (result == null && required) {
+            throw new IllegalArgumentException("Unknown uniform '" + name + "'!");
+        }
+        return result;
+    }
+
+    private VertexAttribute getProgramAttribute(String name, boolean required) {
+        VertexAttribute result = mAttributes.get(name);
+        if (result == null) {
+            int handle = GLES20.glGetAttribLocation(mProgram, name);
+            if (handle >= 0) {
+                result = new VertexAttribute(name, handle);
+                mAttributes.put(name, result);
+            } else if (required) {
+                throw new IllegalArgumentException("Unknown attribute '" + name + "'!");
+            }
+        }
+        return result;
+    }
+
+    private void checkUniformAssignment(ProgramUniform uniform, int values, int components) {
+        if (values % components != 0) {
+            throw new RuntimeException("Size mismatch: Attempting to assign values of size "
+                + values + " to uniform '" + uniform.getName() + "' (must be multiple of "
+                + components + ")!");
+        } else if (uniform.getSize() != values / components) {
+            throw new RuntimeException("Size mismatch: Cannot assign " + values + " values to "
+                + "uniform '" + uniform.getName() + "'!");
+        }
+    }
+
+    private static int strlen(byte[] strVal) {
+        for (int i = 0; i < strVal.length; ++i) {
+            if (strVal[i] == '\0') {
+                return i;
+            }
+        }
+        return strVal.length;
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/InputPort.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/InputPort.java
new file mode 100644
index 0000000..82749c5
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/InputPort.java
@@ -0,0 +1,327 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import java.lang.reflect.Field;
+
+/**
+ * Input ports are the receiving ports of frames in a filter.
+ * <p>
+ * InputPort instances receive Frame data from connected OutputPort instances of a previous filter.
+ * Frames flow from output ports to input ports. Filters can process frame data by calling
+ * {@link #pullFrame()} on an input port. If the input port is set to wait for an input frame
+ * (see {@link #setWaitsForFrame(boolean)}), there is guaranteed to be Frame on the port before
+ * {@code onProcess()} is called. This is the default setting. Otherwise, calling
+ * {@link #pullFrame()} may return a value of {@code null}.
+ * <p/><p>
+ * InputPorts may be bound to fields of the Filter. When an input port is bound to a field, Frame
+ * values will be assigned to the field once a Frame is received on that port. The Frame value must
+ * be of a type that is compatible with the field type.
+ * </p>
+ */
+public final class InputPort {
+
+    private Filter mFilter;
+    private String mName;
+    private Signature.PortInfo mInfo;
+    private FrameListener mListener = null;
+    private FrameQueue.Builder mQueueBuilder = null;
+    private FrameQueue mQueue = null;
+    private boolean mWaitForFrame = true;
+    private boolean mAutoPullEnabled = false;
+
+    public interface FrameListener {
+        public void onFrameReceived(InputPort port, Frame frame);
+    }
+
+    private class FieldBinding implements FrameListener {
+        private Field mField;
+
+        public FieldBinding(Field field) {
+            mField = field;
+        }
+
+        @Override
+        public void onFrameReceived(InputPort port, Frame frame) {
+            try {
+                if(port.mInfo.type.getNumberOfDimensions() > 0) {
+                    FrameValues frameValues = frame.asFrameValues();
+                    mField.set(mFilter, frameValues.getValues());
+                } else {
+                    FrameValue frameValue = frame.asFrameValue();
+                    mField.set(mFilter, frameValue.getValue());
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("Assigning frame " + frame + " to field "
+                    + mField + " of filter " + mFilter + " caused exception!", e);
+            }
+        }
+    }
+
+    /**
+     * Attach this input port to an output port for frame passing.
+     *
+     * Use this method whenever you plan on passing a Frame through from an input port to an
+     * output port. This must be called from inside
+     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}.
+     *
+     * @param outputPort the output port that Frames will be pushed to.
+     */
+    public void attachToOutputPort(OutputPort outputPort) {
+        assertInAttachmentStage();
+        mFilter.openOutputPort(outputPort);
+        mQueueBuilder.attachQueue(outputPort.getQueue());
+    }
+
+    /**
+     * Bind this input port to the specified listener.
+     *
+     * Use this when you wish to be notified of incoming frames. The listener method
+     * {@link FrameListener#onFrameReceived(InputPort, Frame)} will be called once a Frame is pulled
+     * on this port. Typically this is called from inside
+     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
+     * conjunction with {@link #setAutoPullEnabled(boolean)}. Overrides any previous bindings.
+     *
+     * @param listener the listener to handle incoming Frames.
+     */
+    public void bindToListener(FrameListener listener) {
+        assertInAttachmentStage();
+        mListener = listener;
+    }
+
+    /**
+     * Bind this input port to the specified field.
+     *
+     * Use this when you wish to pull frames directly into a field of the filter. This requires
+     * that the input frames can be interpreted as object-based frames of the field's class.
+     * Overrides any previous bindings.
+     *
+     * This is typically called from inside
+     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
+     * conjunction with {@link #setAutoPullEnabled(boolean)}.
+     *
+     * @param field the field to pull frame data into.
+     * @see #bindToFieldNamed(String)
+     * @see #setAutoPullEnabled(boolean)
+     */
+    public void bindToField(Field field) {
+        assertInAttachmentStage();
+        mListener = new FieldBinding(field);
+    }
+
+    /**
+     * Bind this input port to the field with the specified name.
+     *
+     * Use this when you wish to pull frames directly into a field of the filter. This requires
+     * that the input frames can be interpreted as object-based frames of the field's class.
+     * Overrides any previous bindings.
+     *
+     * This is typically called from inside
+     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
+     * conjunction with {@link #setAutoPullEnabled(boolean)}.
+     *
+     * @param fieldName the field to pull frame data into.
+     * @see #bindToField(Field)
+     * @see #setAutoPullEnabled(boolean)
+     */
+    public void bindToFieldNamed(String fieldName) {
+        Field field = findFieldNamed(fieldName, mFilter.getClass());
+        if (field == null) {
+            throw new IllegalArgumentException("Attempting to bind to unknown field '"
+                + fieldName + "'!");
+        }
+        bindToField(field);
+    }
+
+    /**
+     * Set whether the InputPort automatically pulls frames.
+     * This is typically only used when the port is bound to another target.
+     * @param enabled true, if frames should be automatically pulled on this port.
+     */
+    public void setAutoPullEnabled(boolean enabled) {
+        mAutoPullEnabled = enabled;
+    }
+
+    /**
+     * Returns whether the InputPort automatically pulls frames.
+     * @return true, if frames are automatically pulled on this port.
+     */
+    public boolean isAutoPullEnabled() {
+        return mAutoPullEnabled;
+    }
+
+    /**
+     * Pull a waiting a frame from the port.
+     *
+     * Call this to pull a frame from the input port for processing. If no frame is waiting on the
+     * input port, returns null. After this call the port will have no Frame waiting (empty port).
+     * Note, that this returns a frame owned by the input queue. You must detach the frame if you
+     * wish to hold on to it.
+     *
+     * @return Frame instance, or null if no frame is available for pulling.
+     */
+    public synchronized Frame pullFrame() {
+        if (mQueue == null) {
+            throw new IllegalStateException("Cannot pull frame from closed input port!");
+        }
+        Frame frame = mQueue.pullFrame();
+        if (frame != null) {
+            if (mListener != null) {
+                mListener.onFrameReceived(this, frame);
+            }
+            //Log.i("InputPort", "Adding frame " + frame + " to auto-release pool");
+            mFilter.addAutoReleaseFrame(frame);
+            long timestamp = frame.getTimestamp();
+            if (timestamp != Frame.TIMESTAMP_NOT_SET) {
+                mFilter.onPulledFrameWithTimestamp(frame.getTimestamp());
+            }
+        }
+        return frame;
+    }
+
+    public synchronized Frame peek() {
+        if (mQueue == null) {
+            throw new IllegalStateException("Cannot pull frame from closed input port!");
+        }
+        return mQueue.peek();
+    }
+
+    /**
+     * Returns true, if the port is connected.
+     * @return true, if there is an output port that connects to this port.
+     */
+    public boolean isConnected() {
+        return mQueue != null;
+    }
+
+    /**
+     * Returns true, if there is a frame waiting on this port.
+     * @return true, if there is a frame waiting on this port.
+     */
+    public synchronized boolean hasFrame() {
+        return mQueue != null && mQueue.canPull();
+    }
+
+    /**
+     * Sets whether to wait for a frame on this port before processing.
+     * When set to true, the Filter will not be scheduled for processing unless there is a Frame
+     * waiting on this port. The default value is true.
+     *
+     * @param wait true, if the Filter should wait for a Frame before processing.
+     * @see #waitsForFrame()
+     */
+    public void setWaitsForFrame(boolean wait) {
+        mWaitForFrame = wait;
+    }
+
+    /**
+     * Returns whether the filter waits for a frame on this port before processing.
+     * @return true, if the filter waits for a frame on this port before processing.
+     * @see #setWaitsForFrame(boolean)
+     */
+    public boolean waitsForFrame() {
+        return mWaitForFrame;
+    }
+
+    /**
+     * Returns the input port's name.
+     * This is the name that was specified when the input port was connected.
+     *
+     * @return the input port's name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the FrameType of this port.
+     * This is the type that was specified when the input port was declared.
+     *
+     * @return the input port's FrameType.
+     */
+    public FrameType getType() {
+        return getQueue().getType();
+    }
+
+    /**
+     * Return the filter object that this port belongs to.
+     *
+     * @return the input port's filter.
+     */
+    public Filter getFilter() {
+        return mFilter;
+    }
+
+    @Override
+    public String toString() {
+        return mFilter.getName() + ":" + mName;
+    }
+
+    // Internal only ///////////////////////////////////////////////////////////////////////////////
+    InputPort(Filter filter, String name, Signature.PortInfo info) {
+        mFilter = filter;
+        mName = name;
+        mInfo = info;
+    }
+
+    boolean conditionsMet() {
+        return !mWaitForFrame || hasFrame();
+    }
+
+    void onOpen(FrameQueue.Builder builder) {
+        mQueueBuilder = builder;
+        mQueueBuilder.setReadType(mInfo.type);
+        mFilter.onInputPortOpen(this);
+    }
+
+    void setQueue(FrameQueue queue) {
+        mQueue = queue;
+        mQueueBuilder = null;
+    }
+
+    FrameQueue getQueue() {
+        return mQueue;
+    }
+
+    void clear() {
+        if (mQueue != null) {
+            mQueue.clear();
+        }
+    }
+
+    private void assertInAttachmentStage() {
+        if (mQueueBuilder == null) {
+            throw new IllegalStateException("Attempting to attach port while not in attachment "
+                + "stage!");
+        }
+    }
+
+    private Field findFieldNamed(String fieldName, Class<?> clazz) {
+        Field field = null;
+        try {
+            field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null) {
+                field = findFieldNamed(fieldName, superClass);
+            }
+        }
+        return field;
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MffContext.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MffContext.java
new file mode 100644
index 0000000..b7212f9
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MffContext.java
@@ -0,0 +1,470 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.renderscript.RenderScript;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The MffContext holds the state and resources of a Mobile Filter Framework processing instance.
+ * Though it is possible to create multiple MffContext instances, typical applications will rely on
+ * a single MffContext to perform all processing within the Mobile Filter Framework.
+ *
+ * The MffContext class declares two methods {@link #onPause()} and {@link #onResume()}, that are
+ * typically called when the application activity is paused and resumed. This will take care of
+ * halting any processing in the context, and releasing resources while the activity is paused.
+ */
+public class MffContext {
+
+    /**
+     * Class to hold configuration information for MffContexts.
+     */
+    public static class Config {
+        /**
+         * Set to true, if this context will make use of the camera.
+         * If your application does not require the camera, the context does not guarantee that
+         * a camera is available for streaming. That is, you may only use a CameraStreamer if
+         * the context's {@link #isCameraStreamingSupported()} returns true.
+         */
+        public boolean requireCamera = true;
+
+        /**
+         * Set to true, if this context requires OpenGL.
+         * If your application does not require OpenGL, the context does not guarantee that OpenGL
+         * is available. That is, you may only use OpenGL (within filters running in this context)
+         * if the context's {@link #isOpenGLSupported()} method returns true.
+         */
+        public boolean requireOpenGL = true;
+
+        /**
+         * On older Android versions the Camera may need a SurfaceView to render into in order to
+         * function. You may specify a dummy SurfaceView here if you do not want the context to
+         * create its own view. Note, that your view may or may not be used. You cannot rely on
+         * your dummy view to be used by the Camera. If you pass null, no dummy view will be used.
+         * In this case your application may not run correctly on older devices if you use the
+         * camera. This flag has no effect if you do not require the camera.
+         */
+        public SurfaceView dummySurface = null;
+
+        /** Force MFF to not use OpenGL in its processing. */
+        public boolean forceNoGL = false;
+    }
+
+    static private class State {
+        public static final int STATE_RUNNING = 1;
+        public static final int STATE_PAUSED = 2;
+        public static final int STATE_DESTROYED = 3;
+
+        public int current = STATE_RUNNING;
+    }
+
+    /** The application context. */
+    private Context mApplicationContext = null;
+
+    /** The set of filter graphs within this context */
+    private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
+
+    /** The set of graph runners within this context */
+    private Set<GraphRunner> mRunners = new HashSet<GraphRunner>();
+
+    /** True, if the context preserves frames when paused. */
+    private boolean mPreserveFramesOnPause = false;
+
+    /** The shared CameraStreamer that streams camera frames to CameraSource filters. */
+    private CameraStreamer mCameraStreamer = null;
+
+    /** The current context state. */
+    private State mState = new State();
+
+    /** A dummy SurfaceView that is required for Camera operation on older devices. */
+    private SurfaceView mDummySurfaceView = null;
+
+    /** Handler to execute code in the context's thread, such as issuing callbacks. */
+    private Handler mHandler = null;
+
+    /** Flag whether OpenGL ES 2 is supported in this context. */
+    private boolean mGLSupport;
+
+    /** Flag whether camera streaming is supported in this context. */
+    private boolean mCameraStreamingSupport;
+
+    /** RenderScript base master class. */
+    private RenderScript mRenderScript;
+
+    /**
+     * Creates a new MffContext with the default configuration.
+     *
+     * An MffContext must be attached to a Context object of an application. You may create
+     * multiple MffContexts, however data between them cannot be shared. The context must be
+     * created in a thread with a Looper (such as the main/UI thread).
+     *
+     * On older versions of Android, the MffContext may create a visible dummy view for the
+     * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner.
+     *
+     * @param context The application context to attach the MffContext to.
+     */
+    public MffContext(Context context) {
+        init(context, new Config());
+    }
+
+    /**
+     * Creates a new MffContext with the specified configuration.
+     *
+     * An MffContext must be attached to a Context object of an application. You may create
+     * multiple MffContexts, however data between them cannot be shared. The context must be
+     * created in a thread with a Looper (such as the main/UI thread).
+     *
+     * On older versions of Android, the MffContext may create a visible dummy view for the
+     * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner.
+     * You may alternatively specify your own SurfaceView in the configuration.
+     *
+     * @param context The application context to attach the MffContext to.
+     * @param config The configuration to use.
+     *
+     * @throws RuntimeException If no context for the requested configuration could be created.
+     */
+    public MffContext(Context context, Config config) {
+        init(context, config);
+    }
+
+    /**
+     * Put all processing in the context on hold.
+     * This is typically called from your application's <code>onPause()</code> method, and will
+     * stop all running graphs (closing their filters). If the context does not preserve frames on
+     * pause (see {@link #setPreserveFramesOnPause(boolean)}) all frames attached to this context
+     * are released.
+     */
+    public void onPause() {
+        synchronized (mState) {
+            if (mState.current == State.STATE_RUNNING) {
+                if (mCameraStreamer != null) {
+                    mCameraStreamer.halt();
+                }
+                stopRunners(true);
+                mState.current = State.STATE_PAUSED;
+            }
+        }
+    }
+
+    /**
+     * Resumes the processing in this context.
+     * This is typically called from the application's <code>onResume()</code> method, and will
+     * resume processing any of the previously stopped filter graphs.
+     */
+    public void onResume() {
+        synchronized (mState) {
+            if (mState.current == State.STATE_PAUSED) {
+                resumeRunners();
+                resumeCamera();
+                mState.current = State.STATE_RUNNING;
+            }
+        }
+    }
+
+    /**
+     * Release all resources associated with this context.
+     * This will also stop any running graphs.
+     */
+    public void release() {
+        synchronized (mState) {
+            if (mState.current != State.STATE_DESTROYED) {
+                if (mCameraStreamer != null) {
+                    mCameraStreamer.stop();
+                    mCameraStreamer.tearDown();
+                }
+                if (Build.VERSION.SDK_INT >= 11) {
+                    maybeDestroyRenderScript();
+                }
+                stopRunners(false);
+                waitUntilStopped();
+                tearDown();
+                mState.current = State.STATE_DESTROYED;
+            }
+        }
+    }
+
+    /**
+     * Set whether frames are preserved when the context is paused.
+     * When passing false, all Frames associated with this context are released. The default
+     * value is true.
+     *
+     * @param preserve true, to preserve frames when the context is paused.
+     *
+     * @see #getPreserveFramesOnPause()
+     */
+    public void setPreserveFramesOnPause(boolean preserve) {
+        mPreserveFramesOnPause = preserve;
+    }
+
+    /**
+     * Returns whether frames are preserved when the context is paused.
+     *
+     * @return true, if frames are preserved when the context is paused.
+     *
+     * @see #setPreserveFramesOnPause(boolean)
+     */
+    public boolean getPreserveFramesOnPause() {
+        return mPreserveFramesOnPause;
+    }
+
+    /**
+     * Returns the application context that the MffContext is attached to.
+     *
+     * @return The application context for this context.
+     */
+    public Context getApplicationContext() {
+        return mApplicationContext;
+    }
+
+    /**
+     * Returns the context's shared CameraStreamer.
+     * Use the CameraStreamer to control the Camera. Frames from the Camera are typically streamed
+     * to CameraSource filters.
+     *
+     * @return The context's CameraStreamer instance.
+     */
+    public CameraStreamer getCameraStreamer() {
+        if (mCameraStreamer == null) {
+            mCameraStreamer = new CameraStreamer(this);
+        }
+        return mCameraStreamer;
+    }
+
+    /**
+     * Set the default EGL config chooser.
+     *
+     * When an EGL context is required by the MFF, the channel sizes specified here are used. The
+     * default sizes are 8 bits per R,G,B,A channel and 0 bits for depth and stencil channels.
+     *
+     * @param redSize The size of the red channel in bits.
+     * @param greenSize The size of the green channel in bits.
+     * @param blueSize The size of the blue channel in bits.
+     * @param alphaSize The size of the alpha channel in bits.
+     * @param depthSize The size of the depth channel in bits.
+     * @param stencilSize The size of the stencil channel in bits.
+     */
+    public static void setEGLConfigChooser(int redSize,
+                                           int greenSize,
+                                           int blueSize,
+                                           int alphaSize,
+                                           int depthSize,
+                                           int stencilSize) {
+        RenderTarget.setEGLConfigChooser(redSize,
+                                         greenSize,
+                                         blueSize,
+                                         alphaSize,
+                                         depthSize,
+                                         stencilSize);
+    }
+
+    /**
+     * Returns true, if this context supports using OpenGL.
+     * @return true, if this context supports using OpenGL.
+     */
+    public final boolean isOpenGLSupported() {
+        return mGLSupport;
+    }
+
+    /**
+     * Returns true, if this context supports camera streaming.
+     * @return true, if this context supports camera streaming.
+     */
+    public final boolean isCameraStreamingSupported() {
+        return mCameraStreamingSupport;
+    }
+
+    @TargetApi(11)
+    public final RenderScript getRenderScript() {
+        if (mRenderScript == null) {
+            mRenderScript = RenderScript.create(mApplicationContext);
+        }
+        return mRenderScript;
+    }
+
+    final void assertOpenGLSupported() {
+        if (!isOpenGLSupported()) {
+            throw new RuntimeException("Attempting to use OpenGL ES 2 in a context that does not "
+                    + "support it!");
+        }
+    }
+
+    void addGraph(FilterGraph graph) {
+        synchronized (mGraphs) {
+            mGraphs.add(graph);
+        }
+    }
+
+    void addRunner(GraphRunner runner) {
+        synchronized (mRunners) {
+            mRunners.add(runner);
+        }
+    }
+
+    SurfaceView getDummySurfaceView() {
+        return mDummySurfaceView;
+    }
+
+    void postRunnable(Runnable runnable) {
+        mHandler.post(runnable);
+    }
+
+    private void init(Context context, Config config) {
+        determineGLSupport(context, config);
+        determineCameraSupport(config);
+        createHandler();
+        mApplicationContext = context.getApplicationContext();
+        fetchDummySurfaceView(context, config);
+    }
+
+    private void fetchDummySurfaceView(Context context, Config config) {
+        if (config.requireCamera && CameraStreamer.requireDummySurfaceView()) {
+            mDummySurfaceView = config.dummySurface != null
+                    ? config.dummySurface
+                    : createDummySurfaceView(context);
+        }
+    }
+
+    private void determineGLSupport(Context context, Config config) {
+        if (config.forceNoGL) {
+            mGLSupport = false;
+        } else {
+            mGLSupport = getPlatformSupportsGLES2(context);
+            if (config.requireOpenGL && !mGLSupport) {
+                throw new RuntimeException("Cannot create context that requires GL support on "
+                        + "this platform!");
+            }
+        }
+    }
+
+    private void determineCameraSupport(Config config) {
+        mCameraStreamingSupport = (CameraStreamer.getNumberOfCameras() > 0);
+        if (config.requireCamera && !mCameraStreamingSupport) {
+            throw new RuntimeException("Cannot create context that requires a camera on "
+                    + "this platform!");
+        }
+    }
+
+    private static boolean getPlatformSupportsGLES2(Context context) {
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        ConfigurationInfo configurationInfo = am.getDeviceConfigurationInfo();
+        return configurationInfo.reqGlEsVersion >= 0x20000;
+    }
+
+    private void createHandler() {
+        if (Looper.myLooper() == null) {
+            throw new RuntimeException("MffContext must be created in a thread with a Looper!");
+        }
+        mHandler = new Handler();
+    }
+
+    private void stopRunners(boolean haltOnly) {
+        synchronized (mRunners) {
+            // Halt all runners (does nothing if not running)
+            for (GraphRunner runner : mRunners) {
+                if (haltOnly) {
+                    runner.halt();
+                } else {
+                    runner.stop();
+                }
+            }
+            // Flush all graphs if requested (this is queued up after the call to halt)
+            if (!mPreserveFramesOnPause) {
+                for (GraphRunner runner : mRunners) {
+                    runner.flushFrames();
+                }
+            }
+        }
+    }
+
+    private void resumeRunners() {
+        synchronized (mRunners) {
+            for (GraphRunner runner : mRunners) {
+                runner.restart();
+            }
+        }
+    }
+
+    private void resumeCamera() {
+        // Restart only affects previously halted cameras that were running.
+        if (mCameraStreamer != null) {
+            mCameraStreamer.restart();
+        }
+    }
+
+    private void waitUntilStopped() {
+        for (GraphRunner runner : mRunners) {
+            runner.waitUntilStop();
+        }
+    }
+
+    private void tearDown() {
+        // Tear down graphs
+        for (FilterGraph graph : mGraphs) {
+            graph.tearDown();
+        }
+
+        // Tear down runners
+        for (GraphRunner runner : mRunners) {
+            runner.tearDown();
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private SurfaceView createDummySurfaceView(Context context) {
+        // This is only called on Gingerbread devices, so deprecation warning is unnecessary.
+        SurfaceView dummySurfaceView = new SurfaceView(context);
+        dummySurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        // If we have an activity for this context we'll add the SurfaceView to it (as a 1x1 view
+        // in the top-left corner). If not, we warn the user that they may need to add one manually.
+        Activity activity = findActivityForContext(context);
+        if (activity != null) {
+            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(1, 1);
+            activity.addContentView(dummySurfaceView, params);
+        } else {
+            Log.w("MffContext", "Could not find activity for dummy surface! Consider specifying "
+                    + "your own SurfaceView!");
+        }
+        return dummySurfaceView;
+    }
+
+    private Activity findActivityForContext(Context context) {
+        return (context instanceof Activity) ? (Activity) context : null;
+    }
+
+    @TargetApi(11)
+    private void maybeDestroyRenderScript() {
+        if (mRenderScript != null) {
+            mRenderScript.destroy();
+            mRenderScript = null;
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MotionSensor.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MotionSensor.java
new file mode 100644
index 0000000..95558f2
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/MotionSensor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+// Make values from a motion sensor (e.g., accelerometer) available as filter outputs.
+
+package androidx.media.filterpacks.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValues;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public final class MotionSensor extends Filter implements SensorEventListener {
+
+    private SensorManager mSensorManager = null;
+    private Sensor mSensor = null;
+
+    private float[] mValues = new float[3];
+
+    public MotionSensor(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addOutputPort("values", Signature.PORT_REQUIRED, FrameType.array(float.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onPrepare() {
+        mSensorManager = (SensorManager)getContext().getApplicationContext()
+                            .getSystemService(Context.SENSOR_SERVICE);
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
+        // TODO: currently, the type of sensor is hardcoded. Should be able to set the sensor
+        //  type as filter input!
+        mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_UI);
+    }
+
+    @Override
+    protected void onTearDown() {
+        mSensorManager.unregisterListener(this);
+    }
+
+    @Override
+    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // (Do we need to do something when sensor accuracy changes?)
+    }
+
+    @Override
+    public final void onSensorChanged(SensorEvent event) {
+        synchronized(mValues) {
+            mValues[0] = event.values[0];
+            mValues[1] = event.values[1];
+            mValues[2] = event.values[2];
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("values");
+        FrameValues outFrame = outPort.fetchAvailableFrame(null).asFrameValues();
+        synchronized(mValues) {
+            outFrame.setValues(mValues);
+        }
+        outFrame.setTimestamp(System.currentTimeMillis() * 1000000L);
+        outPort.pushFrame(outFrame);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NewChromaHistogramFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NewChromaHistogramFilter.java
new file mode 100644
index 0000000..f524be371
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NewChromaHistogramFilter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+// Extract histogram from image.
+
+package androidx.media.filterpacks.histogram;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+/**
+ * ChromaHistogramFilter takes in an image in HSVA format and computes a 2-D histogram with a
+ * 2 dimensional chroma histogram based on hue (column) and saturation (row) at the top and
+ * a 1-D value histogram in the last row. The number of bin in the value histogram equals to
+ * the number of bins in hue.
+ */
+public final class NewChromaHistogramFilter extends Filter {
+
+    private int mHueBins = 6;
+    private int mSaturationBins = 3;
+    private int mValueBins;
+
+    private int mSaturationThreshold = 26; // 255 * 0.1
+    private int mValueThreshold = 51; // 255 * 0.2
+
+    public NewChromaHistogramFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU);
+        FrameType dataOut = FrameType.buffer2D(FrameType.ELEMENT_FLOAT32);
+
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("huebins", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("saturationbins", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("saturationthreshold", Signature.PORT_OPTIONAL,
+                    FrameType.single(int.class))
+            .addInputPort("valuethreshold", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addOutputPort("histogram", Signature.PORT_REQUIRED, dataOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("huebins")) {
+            port.bindToFieldNamed("mHueBins");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("saturationbins")) {
+            port.bindToFieldNamed("mSaturationBins");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("saturationthreshold")) {
+            port.bindToFieldNamed("mSaturationThreshold");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("valuethreshold")) {
+            port.bindToFieldNamed("mValueThreshold");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameBuffer2D imageFrame = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        OutputPort outPort = getConnectedOutputPort("histogram");
+
+        mValueBins = mHueBins;
+        int[] outDims = new int[] {mHueBins, mSaturationBins + 1};
+        FrameBuffer2D histogramFrame = outPort.fetchAvailableFrame(outDims).asFrameBuffer2D();
+
+        ByteBuffer imageBuffer  = imageFrame.lockBytes(Frame.MODE_READ);
+        ByteBuffer histogramBuffer = histogramFrame.lockBytes(Frame.MODE_READ);
+        histogramBuffer.order(ByteOrder.nativeOrder());
+        FloatBuffer floatHistogram = histogramBuffer.asFloatBuffer();
+
+        // Run native method
+        extractChromaHistogram(imageBuffer, floatHistogram, mHueBins, mSaturationBins, mValueBins,
+                mSaturationThreshold, mValueThreshold);
+
+        imageFrame.unlock();
+        histogramFrame.unlock();
+
+        outPort.pushFrame(histogramFrame);
+    }
+
+    private static native void extractChromaHistogram(ByteBuffer imageBuffer,
+            FloatBuffer histogramBuffer, int hueBins, int saturationBins, int valueBins,
+            int saturationThreshold, int valueThreshold);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NormFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NormFilter.java
new file mode 100644
index 0000000..e816110
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/NormFilter.java
@@ -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.
+ */
+
+package androidx.media.filterpacks.numeric;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+/**
+ * Filter to calculate the 2-norm of the inputs. i.e. sqrt(x^2 + y^2)
+ * TODO: Add support for more norms in the future.
+ */
+public final class NormFilter extends Filter {
+   private static final String TAG = "NormFilter";
+   private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+   public NormFilter(MffContext context, String name) {
+       super(context, name);
+   }
+
+   @Override
+   public Signature getSignature() {
+       FrameType floatT = FrameType.single(float.class);
+       return new Signature()
+           .addInputPort("x", Signature.PORT_REQUIRED, floatT)
+           .addInputPort("y", Signature.PORT_REQUIRED, floatT)
+           .addOutputPort("norm", Signature.PORT_REQUIRED, floatT)
+           .disallowOtherPorts();
+   }
+
+   @Override
+   protected void onProcess() {
+     FrameValue xFrameValue = getConnectedInputPort("x").pullFrame().asFrameValue();
+     float xValue = ((Float)xFrameValue.getValue()).floatValue();
+     FrameValue yFrameValue = getConnectedInputPort("y").pullFrame().asFrameValue();
+     float yValue = ((Float)yFrameValue.getValue()).floatValue();
+
+     float norm = (float) Math.hypot(xValue, yValue);
+     if (mLogVerbose) Log.v(TAG, "Norm = " + norm);
+     OutputPort outPort = getConnectedOutputPort("norm");
+     FrameValue outFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+     outFrame.setValue(norm);
+     outPort.pushFrame(outFrame);
+   }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/OutputPort.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/OutputPort.java
new file mode 100644
index 0000000..06117c3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/OutputPort.java
@@ -0,0 +1,193 @@
+/*
+ * 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 androidx.media.filterfw;
+
+/**
+ * Output ports are the data emitting ports of filters.
+ * <p>
+ * Filters push data frames onto output-ports, which in turn push them onto their connected input
+ * ports. Output ports must be connected to an input port before data can be pushed onto them.
+ * Input and output ports share their Frame slot, meaning that when a frame is waiting on an output
+ * port, it is also waiting on the connected input port.
+ * </p><p>
+ * Only one frame can be pushed onto an output port at a time. In other words, a Frame must first
+ * be consumed by the target filter before a new frame can be pushed on the output port. If the
+ * output port is set to wait until it becomes free (see {@link #setWaitsUntilAvailable(boolean)}),
+ * it is guaranteed to be available when {@code onProcess()} is called. This is the default setting.
+ * </p>
+ */
+public final class OutputPort {
+
+    private Filter mFilter;
+    private String mName;
+    private Signature.PortInfo mInfo;
+    private FrameQueue.Builder mQueueBuilder = null;
+    private FrameQueue mQueue = null;
+    private boolean mWaitsUntilAvailable = true;
+    private InputPort mTarget = null;
+
+    /**
+     * Returns true, if this port is connected to a target port.
+     * @return true, if this port is connected to a target port.
+     */
+    public boolean isConnected() {
+        return mTarget != null;
+    }
+
+    /**
+     * Returns true, if there is no frame waiting on this port.
+     * @return true, if no Frame instance is waiting on this port.
+     */
+    public boolean isAvailable() {
+        return mQueue == null || mQueue.canPush();
+    }
+
+    /**
+     * Returns a frame for writing.
+     *
+     * Call this method to fetch a new frame to write into. When you have finished writing the
+     * frame data, you can push it into the output queue using {@link #pushFrame(Frame)}. Note,
+     * that the Frame returned is owned by the queue. If you wish to hold on to the frame, you
+     * must detach it.
+     *
+     * @param dimensions the size of the Frame you wish to obtain.
+     * @return a writable Frame instance.
+     */
+    public Frame fetchAvailableFrame(int[] dimensions) {
+        Frame frame = getQueue().fetchAvailableFrame(dimensions);
+        if (frame != null) {
+            //Log.i("OutputPort", "Adding frame " + frame + " to auto-release pool");
+            mFilter.addAutoReleaseFrame(frame);
+        }
+        return frame;
+    }
+
+    /**
+     * Pushes a frame onto this output port.
+     *
+     * This is typically a Frame instance you obtained by previously calling
+     * {@link #fetchAvailableFrame(int[])}, but may come from other sources such as an input port
+     * that is attached to this output port.
+     *
+     * Once you have pushed a frame to an output, you may no longer modify it as it may be shared
+     * among other filters.
+     *
+     * @param frame the frame to push to the output queue.
+     */
+    public void pushFrame(Frame frame) {
+        // Some queues allow pushing without fetching, so we need to make sure queue is open
+        // before pushing!
+        long timestamp = frame.getTimestamp();
+        if (timestamp == Frame.TIMESTAMP_NOT_SET)
+            frame.setTimestamp(mFilter.getCurrentTimestamp());
+        getQueue().pushFrame(frame);
+    }
+
+    /**
+     * Sets whether to wait until this port becomes available before processing.
+     * When set to true, the Filter will not be scheduled for processing unless there is no Frame
+     * waiting on this port. The default value is true.
+     *
+     * @param wait true, if filter should wait for the port to become available before processing.
+     * @see #waitsUntilAvailable()
+     */
+    public void setWaitsUntilAvailable(boolean wait) {
+        mWaitsUntilAvailable = wait;
+    }
+
+    /**
+     * Returns whether the filter waits until this port is available before processing.
+     * @return true, if the filter waits until this port is available before processing.
+     * @see #setWaitsUntilAvailable(boolean)
+     */
+    public boolean waitsUntilAvailable() {
+        return mWaitsUntilAvailable;
+    }
+
+    /**
+     * Returns the output port's name.
+     * This is the name that was specified when the output port was connected.
+     *
+     * @return the output port's name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Return the filter object that this port belongs to.
+     *
+     * @return the output port's filter.
+     */
+    public Filter getFilter() {
+        return mFilter;
+    }
+
+    @Override
+    public String toString() {
+        return mFilter.getName() + ":" + mName;
+    }
+
+    OutputPort(Filter filter, String name, Signature.PortInfo info) {
+        mFilter = filter;
+        mName = name;
+        mInfo = info;
+    }
+
+    void setTarget(InputPort target) {
+        mTarget = target;
+    }
+
+    /**
+     * Return the (input) port that this output port is connected to.
+     *
+     * @return the connected port, null if not connected.
+     */
+    public InputPort getTarget() {
+        return mTarget;
+    }
+
+    FrameQueue getQueue() {
+        return mQueue;
+    }
+
+    void setQueue(FrameQueue queue) {
+        mQueue = queue;
+        mQueueBuilder = null;
+    }
+
+    void onOpen(FrameQueue.Builder builder) {
+        mQueueBuilder = builder;
+        mQueueBuilder.setWriteType(mInfo.type);
+        mFilter.onOutputPortOpen(this);
+    }
+
+    boolean isOpen() {
+        return mQueue != null;
+    }
+
+    final boolean conditionsMet() {
+        return !mWaitsUntilAvailable || isAvailable();
+    }
+
+    void clear() {
+        if (mQueue != null) {
+            mQueue.clear();
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/PixelUtils.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/PixelUtils.java
new file mode 100644
index 0000000..88538d4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/PixelUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A collection of utilities to deal with pixel operations on ByteBuffers.
+ */
+public class PixelUtils {
+
+    /**
+     * Copy pixels from one buffer to another, applying a transformation.
+     *
+     * <p>The transformation is specified by specifying the initial offset in the output buffer, the
+     * stride (in pixels) between each pixel, and the stride (in pixels) between each row. The row
+     * stride is measured as the number of pixels between the start of each row.</p>
+     *
+     * <p>Note that this method is native for efficiency reasons. It does NOT do any bounds checking
+     * other than making sure the buffers are of sufficient size. This means that you can corrupt
+     * memory if specifying incorrect stride values!</p>
+     *
+     * @param input The input buffer containing pixel data.
+     * @param output The output buffer to hold the transformed pixel data.
+     * @param width The width of the input image.
+     * @param height The height of the input image.
+     * @param offset The start offset in the output (in pixels)
+     * @param pixStride The stride between each pixel (in pixels)
+     * @param rowStride The stride between the start of each row (in pixels)
+     */
+    public static void copyPixels(ByteBuffer input,
+            ByteBuffer output,
+            int width,
+            int height,
+            int offset,
+            int pixStride,
+            int rowStride) {
+        if (input.remaining() != output.remaining()) {
+            throw new IllegalArgumentException("Input and output buffers must have the same size!");
+        } else if (input.remaining() % 4 != 0) {
+            throw new IllegalArgumentException("Input buffer size must be a multiple of 4!");
+        } else if (output.remaining() % 4 != 0) {
+            throw new IllegalArgumentException("Output buffer size must be a multiple of 4!");
+        } else if ((width * height * 4) != input.remaining()) {
+            throw new IllegalArgumentException(
+                    "Input buffer size does not match given dimensions!");
+        } else if ((width * height * 4) != output.remaining()) {
+            throw new IllegalArgumentException(
+                    "Output buffer size does not match given dimensions!");
+        }
+        nativeCopyPixels(input, output, width, height, offset, pixStride, rowStride);
+    }
+
+    private static native void nativeCopyPixels(ByteBuffer input,
+            ByteBuffer output,
+            int width,
+            int height,
+            int offset,
+            int pixStride,
+            int rowStride);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RenderTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RenderTarget.java
new file mode 100644
index 0000000..ab0546d
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RenderTarget.java
@@ -0,0 +1,444 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.media.MediaRecorder;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.os.Build.VERSION;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+public final class RenderTarget {
+
+    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static final int EGL_OPENGL_ES2_BIT = 4;
+
+    // Pre-HC devices do not necessarily support multiple display surfaces.
+    private static boolean mSupportsMultipleDisplaySurfaces = (VERSION.SDK_INT >= 11);
+
+    /** A Map that tracks which objects are wrapped by EGLSurfaces */
+    private static HashMap<Object, EGLSurface> mSurfaceSources = new HashMap<Object, EGLSurface>();
+
+    /** A Map for performing reference counting over shared objects across RenderTargets */
+    private static HashMap<Object, Integer> mRefCounts = new HashMap<Object, Integer>();
+
+    /** Stores the RenderTarget that is focused on the current thread. */
+    private static ThreadLocal<RenderTarget> mCurrentTarget = new ThreadLocal<RenderTarget>();
+
+    /** The source for the surface used in this target (if any) */
+    private Object mSurfaceSource = null;
+
+    /** The cached EGLConfig instance. */
+    private static EGLConfig mEglConfig = null;
+
+    /** The display for which the EGLConfig was chosen. We expect only one. */
+    private static EGLDisplay mConfiguredDisplay;
+
+    private EGL10 mEgl;
+    private EGLDisplay mDisplay;
+    private EGLContext mContext;
+    private EGLSurface mSurface;
+    private int mFbo;
+
+    private boolean mOwnsContext;
+    private boolean mOwnsSurface;
+
+    private static HashMap<EGLContext, ImageShader> mIdShaders
+        = new HashMap<EGLContext, ImageShader>();
+
+    private static HashMap<EGLContext, EGLSurface> mDisplaySurfaces
+        = new HashMap<EGLContext, EGLSurface>();
+
+    private static int sRedSize = 8;
+    private static int sGreenSize = 8;
+    private static int sBlueSize = 8;
+    private static int sAlphaSize = 8;
+    private static int sDepthSize = 0;
+    private static int sStencilSize = 0;
+
+    public static RenderTarget newTarget(int width, int height) {
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        EGLDisplay eglDisplay = createDefaultDisplay(egl);
+        EGLConfig eglConfig = chooseEglConfig(egl, eglDisplay);
+        EGLContext eglContext = createContext(egl, eglDisplay, eglConfig);
+        EGLSurface eglSurface = createSurface(egl, eglDisplay, width, height);
+        RenderTarget result = new RenderTarget(eglDisplay, eglContext, eglSurface, 0, true, true);
+        result.addReferenceTo(eglSurface);
+        return result;
+    }
+
+    public static RenderTarget currentTarget() {
+        // As RenderTargets are immutable, we can safely return the last focused instance on this
+        // thread, as we know it cannot have changed, and therefore must be current.
+        return mCurrentTarget.get();
+    }
+
+    public RenderTarget forTexture(TextureSource texture, int width, int height) {
+        // NOTE: We do not need to lookup any previous bindings of this texture to an FBO, as
+        // multiple FBOs to a single texture is valid.
+        int fbo = GLToolbox.generateFbo();
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
+        GLToolbox.checkGlError("glBindFramebuffer");
+        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,
+                                      GLES20.GL_COLOR_ATTACHMENT0,
+                                      texture.getTarget(),
+                                      texture.getTextureId(),
+                                      0);
+        GLToolbox.checkGlError("glFramebufferTexture2D");
+        return new RenderTarget(mDisplay, mContext, surface(), fbo, false, false);
+    }
+
+    public RenderTarget forSurfaceHolder(SurfaceHolder surfaceHolder) {
+        EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
+        EGLSurface eglSurf = null;
+        synchronized (mSurfaceSources) {
+            eglSurf = mSurfaceSources.get(surfaceHolder);
+            if (eglSurf == null) {
+                eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceHolder, null);
+                mSurfaceSources.put(surfaceHolder, eglSurf);
+            }
+        }
+        checkEglError(mEgl, "eglCreateWindowSurface");
+        checkSurface(mEgl, eglSurf);
+        RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
+        result.addReferenceTo(eglSurf);
+        result.setSurfaceSource(surfaceHolder);
+        return result;
+    }
+
+    @TargetApi(11)
+    public RenderTarget forSurfaceTexture(SurfaceTexture surfaceTexture) {
+        EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
+        EGLSurface eglSurf = null;
+        synchronized (mSurfaceSources) {
+            eglSurf = mSurfaceSources.get(surfaceTexture);
+            if (eglSurf == null) {
+                eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceTexture, null);
+                mSurfaceSources.put(surfaceTexture, eglSurf);
+            }
+        }
+        checkEglError(mEgl, "eglCreateWindowSurface");
+        checkSurface(mEgl, eglSurf);
+        RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
+        result.setSurfaceSource(surfaceTexture);
+        result.addReferenceTo(eglSurf);
+        return result;
+    }
+
+    @TargetApi(11)
+    public RenderTarget forSurface(Surface surface) {
+        EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
+        EGLSurface eglSurf = null;
+        synchronized (mSurfaceSources) {
+            eglSurf = mSurfaceSources.get(surface);
+            if (eglSurf == null) {
+                eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surface, null);
+                mSurfaceSources.put(surface, eglSurf);
+            }
+        }
+        checkEglError(mEgl, "eglCreateWindowSurface");
+        checkSurface(mEgl, eglSurf);
+        RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
+        result.setSurfaceSource(surface);
+        result.addReferenceTo(eglSurf);
+        return result;
+    }
+
+    public static RenderTarget forMediaRecorder(MediaRecorder mediaRecorder) {
+        throw new RuntimeException("Not yet implemented MediaRecorder -> RenderTarget!");
+    }
+
+    public static void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize,
+            int depthSize, int stencilSize) {
+        sRedSize = redSize;
+        sGreenSize = greenSize;
+        sBlueSize = blueSize;
+        sAlphaSize = alphaSize;
+        sDepthSize = depthSize;
+        sStencilSize = stencilSize;
+    }
+
+    public void registerAsDisplaySurface() {
+        if (!mSupportsMultipleDisplaySurfaces) {
+            // Note that while this does in effect change RenderTarget instances (by modifying
+            // their returned EGLSurface), breaking the immutability requirement, it does not modify
+            // the current target. This is important so that the instance returned in
+            // currentTarget() remains accurate.
+            EGLSurface currentSurface = mDisplaySurfaces.get(mContext);
+            if (currentSurface != null && !currentSurface.equals(mSurface)) {
+                throw new RuntimeException("This device supports only a single display surface!");
+            } else {
+                mDisplaySurfaces.put(mContext, mSurface);
+            }
+        }
+    }
+
+    public void unregisterAsDisplaySurface() {
+        if (!mSupportsMultipleDisplaySurfaces) {
+            mDisplaySurfaces.put(mContext, null);
+        }
+    }
+
+    public void focus() {
+        RenderTarget current = mCurrentTarget.get();
+        // We assume RenderTargets are immutable, so that we do not need to focus if the current
+        // RenderTarget has not changed.
+        if (current != this) {
+            mEgl.eglMakeCurrent(mDisplay, surface(), surface(), mContext);
+            mCurrentTarget.set(this);
+        }
+        if (getCurrentFbo() != mFbo) {
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFbo);
+            GLToolbox.checkGlError("glBindFramebuffer");
+        }
+    }
+
+    public static void focusNone() {
+        EGL10 egl = (EGL10) EGLContext.getEGL();
+        egl.eglMakeCurrent(egl.eglGetCurrentDisplay(),
+                           EGL10.EGL_NO_SURFACE,
+                           EGL10.EGL_NO_SURFACE,
+                           EGL10.EGL_NO_CONTEXT);
+        mCurrentTarget.set(null);
+        checkEglError(egl, "eglMakeCurrent");
+    }
+
+    public void swapBuffers() {
+        mEgl.eglSwapBuffers(mDisplay, surface());
+    }
+
+    public EGLContext getContext() {
+        return mContext;
+    }
+
+    public static EGLContext currentContext() {
+        RenderTarget current = RenderTarget.currentTarget();
+        return current != null ? current.getContext() : EGL10.EGL_NO_CONTEXT;
+    }
+
+    public void release() {
+        if (mOwnsContext) {
+            mEgl.eglDestroyContext(mDisplay, mContext);
+            mContext = EGL10.EGL_NO_CONTEXT;
+        }
+        if (mOwnsSurface) {
+            synchronized (mSurfaceSources) {
+                if (removeReferenceTo(mSurface)) {
+                    mEgl.eglDestroySurface(mDisplay, mSurface);
+                    mSurface = EGL10.EGL_NO_SURFACE;
+                    mSurfaceSources.remove(mSurfaceSource);
+                }
+            }
+        }
+        if (mFbo != 0) {
+           GLToolbox.deleteFbo(mFbo);
+       }
+    }
+
+    public void readPixelData(ByteBuffer pixels, int width, int height) {
+        GLToolbox.readTarget(this, pixels, width, height);
+    }
+
+    public ByteBuffer getPixelData(int width, int height) {
+        ByteBuffer pixels = ByteBuffer.allocateDirect(width * height * 4);
+        GLToolbox.readTarget(this, pixels, width, height);
+        return pixels;
+    }
+
+    /**
+     * Returns an identity shader for this context.
+     * You must not modify this shader. Use {@link ImageShader#createIdentity()} if you need to
+     * modify an identity shader.
+     */
+    public ImageShader getIdentityShader() {
+        ImageShader idShader = mIdShaders.get(mContext);
+        if (idShader == null) {
+            idShader = ImageShader.createIdentity();
+            mIdShaders.put(mContext, idShader);
+        }
+        return idShader;
+    }
+
+    @Override
+    public String toString() {
+        return "RenderTarget(" + mDisplay + ", " + mContext + ", " + mSurface + ", " + mFbo + ")";
+    }
+
+    private void setSurfaceSource(Object source) {
+        mSurfaceSource = source;
+    }
+
+    private void addReferenceTo(Object object) {
+        Integer refCount = mRefCounts.get(object);
+        if (refCount != null) {
+            mRefCounts.put(object, refCount + 1);
+        } else {
+            mRefCounts.put(object, 1);
+        }
+    }
+
+    private boolean removeReferenceTo(Object object) {
+        Integer refCount = mRefCounts.get(object);
+        if (refCount != null && refCount > 0) {
+            --refCount;
+            mRefCounts.put(object, refCount);
+            return refCount == 0;
+        } else {
+            Log.e("RenderTarget", "Removing reference of already released: " + object + "!");
+            return false;
+        }
+    }
+
+    private static EGLConfig chooseEglConfig(EGL10 egl, EGLDisplay display) {
+        if (mEglConfig == null || !display.equals(mConfiguredDisplay)) {
+            int[] configsCount = new int[1];
+            EGLConfig[] configs = new EGLConfig[1];
+            int[] configSpec = getDesiredConfig();
+            if (!egl.eglChooseConfig(display, configSpec, configs, 1, configsCount)) {
+                throw new IllegalArgumentException("EGL Error: eglChooseConfig failed " +
+                        getEGLErrorString(egl, egl.eglGetError()));
+            } else if (configsCount[0] > 0) {
+                mEglConfig = configs[0];
+                mConfiguredDisplay = display;
+            }
+        }
+        return mEglConfig;
+    }
+
+    private static int[] getDesiredConfig() {
+        return new int[] {
+                EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_RED_SIZE, sRedSize,
+                EGL10.EGL_GREEN_SIZE, sGreenSize,
+                EGL10.EGL_BLUE_SIZE, sBlueSize,
+                EGL10.EGL_ALPHA_SIZE, sAlphaSize,
+                EGL10.EGL_DEPTH_SIZE, sDepthSize,
+                EGL10.EGL_STENCIL_SIZE, sStencilSize,
+                EGL10.EGL_NONE
+        };
+    }
+
+    private RenderTarget(EGLDisplay display, EGLContext context, EGLSurface surface, int fbo,
+                         boolean ownsContext, boolean ownsSurface) {
+        mEgl = (EGL10) EGLContext.getEGL();
+        mDisplay = display;
+        mContext = context;
+        mSurface = surface;
+        mFbo = fbo;
+        mOwnsContext = ownsContext;
+        mOwnsSurface = ownsSurface;
+    }
+
+    private EGLSurface surface() {
+        if (mSupportsMultipleDisplaySurfaces) {
+            return mSurface;
+        } else {
+            EGLSurface displaySurface = mDisplaySurfaces.get(mContext);
+            return displaySurface != null ? displaySurface : mSurface;
+        }
+    }
+
+    private static void initEgl(EGL10 egl, EGLDisplay display) {
+        int[] version = new int[2];
+        if (!egl.eglInitialize(display, version)) {
+            throw new RuntimeException("EGL Error: eglInitialize failed " +
+                    getEGLErrorString(egl, egl.eglGetError()));
+        }
+    }
+
+    private static EGLDisplay createDefaultDisplay(EGL10 egl) {
+        EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        checkDisplay(egl, display);
+        initEgl(egl, display);
+        return display;
+    }
+
+    private static EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
+        int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+        EGLContext ctxt = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list);
+        checkContext(egl, ctxt);
+        return ctxt;
+    }
+
+    private static EGLSurface createSurface(EGL10 egl, EGLDisplay display, int width, int height) {
+        EGLConfig eglConfig = chooseEglConfig(egl, display);
+        int[] attribs = { EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE };
+        return egl.eglCreatePbufferSurface(display, eglConfig, attribs);
+    }
+
+    private static int getCurrentFbo() {
+        int[] result = new int[1];
+        GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0);
+        return result[0];
+    }
+
+    private static void checkDisplay(EGL10 egl, EGLDisplay display) {
+        if (display == EGL10.EGL_NO_DISPLAY) {
+            throw new RuntimeException("EGL Error: Bad display: "
+                    + getEGLErrorString(egl, egl.eglGetError()));
+        }
+    }
+
+    private static void checkContext(EGL10 egl, EGLContext context) {
+        if (context == EGL10.EGL_NO_CONTEXT) {
+            throw new RuntimeException("EGL Error: Bad context: "
+                    + getEGLErrorString(egl, egl.eglGetError()));
+        }
+    }
+
+    private static void checkSurface(EGL10 egl, EGLSurface surface) {
+        if (surface == EGL10.EGL_NO_SURFACE) {
+            throw new RuntimeException("EGL Error: Bad surface: "
+                    + getEGLErrorString(egl, egl.eglGetError()));
+        }
+    }
+
+    private static void checkEglError(EGL10 egl, String command) {
+        int error = egl.eglGetError();
+        if (error != EGL10.EGL_SUCCESS) {
+            throw new RuntimeException("Error executing " + command + "! EGL error = 0x"
+                + Integer.toHexString(error));
+        }
+    }
+
+    private static String getEGLErrorString(EGL10 egl, int eglError) {
+        if (VERSION.SDK_INT >= 14) {
+            return getEGLErrorStringICS(egl, eglError);
+        } else {
+            return "EGL Error 0x" + Integer.toHexString(eglError);
+        }
+    }
+
+    @TargetApi(14)
+    private static String getEGLErrorStringICS(EGL10 egl, int eglError) {
+        return GLUtils.getEGLErrorString(egl.eglGetError());
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ResizeFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ResizeFilter.java
new file mode 100644
index 0000000..c334c91
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ResizeFilter.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 androidx.media.filterpacks.transform;
+
+import androidx.media.filterfw.*;
+
+// TODO: In the future this could be done with a meta-filter that simply "hard-codes" the crop
+// parameters.
+public class ResizeFilter extends CropFilter {
+
+    public ResizeFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("outputWidth", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("outputHeight", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .addInputPort("useMipmaps", Signature.PORT_OPTIONAL, FrameType.single(boolean.class))
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RotateFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RotateFilter.java
new file mode 100644
index 0000000..5db20a4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/RotateFilter.java
@@ -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.
+ */
+
+package androidx.media.filterpacks.transform;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.geometry.Quad;
+
+public class RotateFilter extends Filter {
+
+    private Quad mSourceRect = Quad.fromRect(0f, 0f, 1f, 1f);
+    private float mRotateAngle = 0;
+    private ImageShader mShader;
+
+    public RotateFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("rotateAngle", Signature.PORT_REQUIRED, FrameType.single(float.class))
+            .addInputPort("sourceRect", Signature.PORT_OPTIONAL, FrameType.single(Quad.class))
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("rotateAngle")) {
+            port.bindToFieldNamed("mRotateAngle");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("sourceRect")) {
+            port.bindToFieldNamed("mSourceRect");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onPrepare() {
+        mShader = ImageShader.createIdentity();
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("image");
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        int[] inDims = inputImage.getDimensions();
+
+        FrameImage2D outputImage = outPort.fetchAvailableFrame(inDims).asFrameImage2D();
+        mShader.setSourceQuad(mSourceRect);
+        Quad targetQuad = mSourceRect.rotated((float) (mRotateAngle / 180 * Math.PI));
+        mShader.setTargetQuad(targetQuad);
+        mShader.process(inputImage, outputImage);
+        outPort.pushFrame(outputImage);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ScaleFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ScaleFilter.java
new file mode 100644
index 0000000..1c3f328
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ScaleFilter.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 androidx.media.filterpacks.transform;
+
+// TODO: scale filter needs to be able to specify output width and height
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.Signature;
+
+public class ScaleFilter extends ResizeFilter {
+
+    private float mScale = 1.0f;
+
+    public ScaleFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+            .addInputPort("scale", Signature.PORT_OPTIONAL, FrameType.single(float.class))
+            .addInputPort("useMipmaps", Signature.PORT_OPTIONAL, FrameType.single(boolean.class))
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("scale")) {
+            port.bindToFieldNamed("mScale");
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("useMipmaps")) {
+            port.bindToFieldNamed("mUseMipmaps");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected int getOutputWidth(int inWidth, int inHeight) {
+        return (int)(inWidth * mScale);
+    }
+
+    @Override
+    protected int getOutputHeight(int inWidth, int inHeight) {
+        return (int)(inHeight * mScale);
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Signature.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Signature.java
new file mode 100644
index 0000000..2c2916f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Signature.java
@@ -0,0 +1,241 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * A Signature holds the specification for a filter's input and output ports.
+ *
+ * A Signature instance must be returned by the filter's {@link Filter#getSignature()} method. It
+ * specifies the number and names of the filter's input and output ports, whether or not they
+ * are required, how data for those ports are accessed, and more. A Signature does not change over
+ * time. This makes Signatures useful for understanding how a filter can be integrated into a
+ * graph.
+ *
+ * There are a number of flags that can be specified for each input and output port. The flag
+ * {@code PORT_REQUIRED} indicates that the user must connect the specified port. On the other hand,
+ * {@code PORT_OPTIONAL} indicates that a port may be connected by the user.
+ *
+ * If ports other than the ones in the Signature are allowed, they default to the most generic
+ * format, that allows passing in any type of Frame. Thus, if more granular access is needed to
+ * a frame's data, it must be specified in the Signature.
+ */
+public class Signature {
+
+    private HashMap<String, PortInfo> mInputPorts = null;
+    private HashMap<String, PortInfo> mOutputPorts = null;
+    private boolean mAllowOtherInputs = true;
+    private boolean mAllowOtherOutputs = true;
+
+    static class PortInfo {
+        public int flags;
+        public FrameType type;
+
+        public PortInfo() {
+            flags = 0;
+            type = FrameType.any();
+        }
+
+        public PortInfo(int flags, FrameType type) {
+            this.flags = flags;
+            this.type = type;
+        }
+
+        public boolean isRequired() {
+            return (flags & PORT_REQUIRED) != 0;
+        }
+
+        public String toString(String ioMode, String name) {
+            String ioName = ioMode + " " + name;
+            String modeName = isRequired() ? "required" : "optional";
+            return modeName + " " + ioName + ": " + type.toString();
+        }
+    }
+
+    /** Indicates that the port must be connected in the graph. */
+    public static final int PORT_REQUIRED = 0x02;
+    /** Indicates that the port may be connected in the graph . */
+    public static final int PORT_OPTIONAL = 0x01;
+
+    /**
+     * Creates a new empty Signature.
+     */
+    public Signature() {
+    }
+
+    /**
+     * Adds an input port to the Signature.
+     *
+     * @param name the name of the input port. Must be unique among input port names.
+     * @param flags a combination of port flags.
+     * @param type the type of the input frame.
+     * @return this Signature instance.
+     */
+    public Signature addInputPort(String name, int flags, FrameType type) {
+        addInputPort(name, new PortInfo(flags, type));
+        return this;
+    }
+
+    /**
+     * Adds an output port to the Signature.
+     *
+     * @param name the name of the output port. Must be unique among output port names.
+     * @param flags a combination of port flags.
+     * @param type the type of the output frame.
+     * @return this Signature instance.
+     */
+    public Signature addOutputPort(String name, int flags, FrameType type) {
+        addOutputPort(name, new PortInfo(flags, type));
+        return this;
+    }
+
+    /**
+     * Disallows the user from adding any other input ports.
+     * Adding any input port not explicitly specified in this Signature will cause an error.
+     * @return this Signature instance.
+     */
+    public Signature disallowOtherInputs() {
+        mAllowOtherInputs = false;
+        return this;
+    }
+
+    /**
+     * Disallows the user from adding any other output ports.
+     * Adding any output port not explicitly specified in this Signature will cause an error.
+     * @return this Signature instance.
+     */
+    public Signature disallowOtherOutputs() {
+        mAllowOtherOutputs = false;
+        return this;
+    }
+
+    /**
+     * Disallows the user from adding any other ports.
+     * Adding any input or output port not explicitly specified in this Signature will cause an
+     * error.
+     * @return this Signature instance.
+     */
+    public Signature disallowOtherPorts() {
+        mAllowOtherInputs = false;
+        mAllowOtherOutputs = false;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer stringBuffer = new StringBuffer();
+        for (Entry<String, PortInfo> entry : mInputPorts.entrySet()) {
+            stringBuffer.append(entry.getValue().toString("input", entry.getKey()) + "\n");
+        }
+        for (Entry<String, PortInfo> entry : mOutputPorts.entrySet()) {
+            stringBuffer.append(entry.getValue().toString("output", entry.getKey()) + "\n");
+        }
+        if (!mAllowOtherInputs) {
+            stringBuffer.append("disallow other inputs\n");
+        }
+        if (!mAllowOtherOutputs) {
+            stringBuffer.append("disallow other outputs\n");
+        }
+        return stringBuffer.toString();
+    }
+
+    PortInfo getInputPortInfo(String name) {
+        PortInfo result = mInputPorts != null ? mInputPorts.get(name) : null;
+        return result != null ? result : new PortInfo();
+    }
+
+    PortInfo getOutputPortInfo(String name) {
+        PortInfo result = mOutputPorts != null ? mOutputPorts.get(name) : null;
+        return result != null ? result : new PortInfo();
+    }
+
+    void checkInputPortsConform(Filter filter) {
+        Set<String> filterInputs = new HashSet<String>();
+        filterInputs.addAll(filter.getConnectedInputPortMap().keySet());
+        if (mInputPorts != null) {
+            for (Entry<String, PortInfo> entry : mInputPorts.entrySet()) {
+                String portName = entry.getKey();
+                PortInfo portInfo = entry.getValue();
+                InputPort inputPort = filter.getConnectedInputPort(portName);
+                if (inputPort == null && portInfo.isRequired()) {
+                    throw new RuntimeException("Filter " + filter + " does not have required "
+                        + "input port '" + portName + "'!");
+                }
+                filterInputs.remove(portName);
+            }
+        }
+        if (!mAllowOtherInputs && !filterInputs.isEmpty()) {
+            throw new RuntimeException("Filter " + filter + " has invalid input ports: "
+                + filterInputs + "!");
+        }
+    }
+
+    void checkOutputPortsConform(Filter filter) {
+        Set<String> filterOutputs = new HashSet<String>();
+        filterOutputs.addAll(filter.getConnectedOutputPortMap().keySet());
+        if (mOutputPorts != null) {
+            for (Entry<String, PortInfo> entry : mOutputPorts.entrySet()) {
+                String portName = entry.getKey();
+                PortInfo portInfo = entry.getValue();
+                OutputPort outputPort = filter.getConnectedOutputPort(portName);
+                if (outputPort == null && portInfo.isRequired()) {
+                    throw new RuntimeException("Filter " + filter + " does not have required "
+                        + "output port '" + portName + "'!");
+                }
+                filterOutputs.remove(portName);
+            }
+        }
+        if (!mAllowOtherOutputs && !filterOutputs.isEmpty()) {
+            throw new RuntimeException("Filter " + filter + " has invalid output ports: "
+                + filterOutputs + "!");
+        }
+    }
+
+    HashMap<String, PortInfo> getInputPorts() {
+        return mInputPorts;
+    }
+
+    HashMap<String, PortInfo> getOutputPorts() {
+        return mOutputPorts;
+    }
+
+    private void addInputPort(String name, PortInfo portInfo) {
+        if (mInputPorts == null) {
+            mInputPorts = new HashMap<String, PortInfo>();
+        }
+        if (mInputPorts.containsKey(name)) {
+            throw new RuntimeException("Attempting to add duplicate input port '" + name + "'!");
+        }
+        mInputPorts.put(name, portInfo);
+    }
+
+    private void addOutputPort(String name, PortInfo portInfo) {
+        if (mOutputPorts == null) {
+            mOutputPorts = new HashMap<String, PortInfo>();
+        }
+        if (mOutputPorts.containsKey(name)) {
+            throw new RuntimeException("Attempting to add duplicate output port '" + name + "'!");
+        }
+        mOutputPorts.put(name, portInfo);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SimpleCache.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SimpleCache.java
new file mode 100644
index 0000000..f54621f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SimpleCache.java
@@ -0,0 +1,38 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * This is a simple LRU cache that is used internally for managing repetitive objects.
+ */
+class SimpleCache<K, V> extends LinkedHashMap<K, V> {
+
+    private int mMaxEntries;
+
+    public SimpleCache(final int maxEntries) {
+        super(maxEntries + 1, 1f, true);
+        mMaxEntries = maxEntries;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
+        return super.size() > mMaxEntries;
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SlotFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SlotFilter.java
new file mode 100644
index 0000000..aaa87c2
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SlotFilter.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 androidx.media.filterfw;
+
+public abstract class SlotFilter extends Filter {
+
+    protected final String mSlotName;
+
+    protected SlotFilter(MffContext context, String name, String slotName) {
+        super(context, name);
+        mSlotName = slotName;
+    }
+
+    protected final FrameType getSlotType() {
+        return getFrameManager().getSlot(mSlotName).getType();
+    }
+
+    protected final boolean slotHasFrame() {
+        return getFrameManager().getSlot(mSlotName).hasFrame();
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SobelFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SobelFilter.java
new file mode 100644
index 0000000..a4c39a1
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SobelFilter.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterpacks.image;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+
+public class SobelFilter extends Filter {
+
+    private static final String mGradientXSource =
+              "precision mediump float;\n"
+            + "uniform sampler2D tex_sampler_0;\n"
+            + "uniform vec2 pix;\n"
+            + "varying vec2 v_texcoord;\n"
+            + "void main() {\n"
+            + "  vec4 a1 = -1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(-pix.x, -pix.y));\n"
+            + "  vec4 a2 = -2.0 * texture2D(tex_sampler_0, v_texcoord + vec2(-pix.x, 0.0));\n"
+            + "  vec4 a3 = -1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(-pix.x, +pix.y));\n"
+            + "  vec4 b1 = +1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(+pix.x, -pix.y));\n"
+            + "  vec4 b2 = +2.0 * texture2D(tex_sampler_0, v_texcoord + vec2(+pix.x, 0.0));\n"
+            + "  vec4 b3 = +1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(+pix.x, +pix.y));\n"
+            + "  gl_FragColor = 0.5 + (a1 + a2 + a3 + b1 + b2 + b3) / 8.0;\n"
+            + "}\n";
+
+    private static final String mGradientYSource =
+              "precision mediump float;\n"
+            + "uniform sampler2D tex_sampler_0;\n"
+            + "uniform vec2 pix;\n"
+            + "varying vec2 v_texcoord;\n"
+            + "void main() {\n"
+            + "  vec4 a1 = -1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(-pix.x, -pix.y));\n"
+            + "  vec4 a2 = -2.0 * texture2D(tex_sampler_0, v_texcoord + vec2(0.0,    -pix.y));\n"
+            + "  vec4 a3 = -1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(+pix.x, -pix.y));\n"
+            + "  vec4 b1 = +1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(-pix.x, +pix.y));\n"
+            + "  vec4 b2 = +2.0 * texture2D(tex_sampler_0, v_texcoord + vec2(0.0,    +pix.y));\n"
+            + "  vec4 b3 = +1.0 * texture2D(tex_sampler_0, v_texcoord + vec2(+pix.x, +pix.y));\n"
+            + "  gl_FragColor = 0.5 + (a1 + a2 + a3 + b1 + b2 + b3) / 8.0;\n"
+            + "}\n";
+
+    private static final String mMagnitudeSource =
+            "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 gx = 2.0 * texture2D(tex_sampler_0, v_texcoord) - 1.0;\n"
+          + "  vec4 gy = 2.0 * texture2D(tex_sampler_1, v_texcoord) - 1.0;\n"
+          + "  gl_FragColor = vec4(sqrt(gx.rgb * gx.rgb + gy.rgb * gy.rgb), 1.0);\n"
+          + "}\n";
+
+    private static final String mDirectionSource =
+            "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 gy = 2.0 * texture2D(tex_sampler_1, v_texcoord) - 1.0;\n"
+          + "  vec4 gx = 2.0 * texture2D(tex_sampler_0, v_texcoord) - 1.0;\n"
+          + "  gl_FragColor = vec4((atan(gy.rgb, gx.rgb) + 3.14) / (2.0 * 3.14), 1.0);\n"
+          + "}\n";
+
+    private ImageShader mGradientXShader;
+    private ImageShader mGradientYShader;
+    private ImageShader mMagnitudeShader;
+    private ImageShader mDirectionShader;
+
+    private FrameType mImageType;
+
+    public SobelFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        // TODO: we will address the issue of READ_GPU / WRITE_GPU when using CPU filters later.
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+        return new Signature().addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+                .addOutputPort("direction", Signature.PORT_OPTIONAL, imageOut)
+                .addOutputPort("magnitude", Signature.PORT_OPTIONAL, imageOut).disallowOtherPorts();
+    }
+
+    @Override
+    protected void onPrepare() {
+        if (isOpenGLSupported()) {
+            mGradientXShader = new ImageShader(mGradientXSource);
+            mGradientYShader = new ImageShader(mGradientYSource);
+            mMagnitudeShader = new ImageShader(mMagnitudeSource);
+            mDirectionShader = new ImageShader(mDirectionSource);
+            mImageType = FrameType.image2D(
+                    FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU | FrameType.WRITE_GPU);
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort magnitudePort = getConnectedOutputPort("magnitude");
+        OutputPort directionPort = getConnectedOutputPort("direction");
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        int[] inputDims = inputImage.getDimensions();
+
+        FrameImage2D magImage = (magnitudePort != null) ?
+                magnitudePort.fetchAvailableFrame(inputDims).asFrameImage2D() : null;
+        FrameImage2D dirImage = (directionPort != null) ?
+                directionPort.fetchAvailableFrame(inputDims).asFrameImage2D() : null;
+        if (isOpenGLSupported()) {
+            FrameImage2D gxFrame = Frame.create(mImageType, inputDims).asFrameImage2D();
+            FrameImage2D gyFrame = Frame.create(mImageType, inputDims).asFrameImage2D();
+            mGradientXShader.setUniformValue("pix", new float[] {1f/inputDims[0], 1f/inputDims[1]});
+            mGradientYShader.setUniformValue("pix", new float[] {1f/inputDims[0], 1f/inputDims[1]});
+            mGradientXShader.process(inputImage, gxFrame);
+            mGradientYShader.process(inputImage, gyFrame);
+            FrameImage2D[] gradientFrames = new FrameImage2D[] { gxFrame, gyFrame };
+            if (magnitudePort != null) {
+                mMagnitudeShader.processMulti(gradientFrames, magImage);
+            }
+            if (directionPort != null) {
+                mDirectionShader.processMulti(gradientFrames, dirImage);
+            }
+            gxFrame.release();
+            gyFrame.release();
+        } else {
+            ByteBuffer inputBuffer  = inputImage.lockBytes(Frame.MODE_READ);
+            ByteBuffer magBuffer  = (magImage != null) ?
+                    magImage.lockBytes(Frame.MODE_WRITE) : null;
+            ByteBuffer dirBuffer  = (dirImage != null) ?
+                    dirImage.lockBytes(Frame.MODE_WRITE) : null;
+            sobelOperator(inputImage.getWidth(), inputImage.getHeight(),
+                    inputBuffer, magBuffer, dirBuffer);
+            inputImage.unlock();
+            if (magImage != null) {
+                magImage.unlock();
+            }
+            if (dirImage != null) {
+                dirImage.unlock();
+            }
+        }
+        if (magImage != null) {
+            magnitudePort.pushFrame(magImage);
+        }
+        if (dirImage != null) {
+            directionPort.pushFrame(dirImage);
+        }
+    }
+
+    private static native boolean sobelOperator(int width, int height,
+            ByteBuffer imageBuffer, ByteBuffer magBuffer, ByteBuffer dirBudder);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/StatsFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/StatsFilter.java
new file mode 100644
index 0000000..94030c3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/StatsFilter.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+// Calculates the mean and standard deviation of the values in the input image.
+// It takes in an RGBA image, but assumes that r, g, b, a are all the same values.
+
+package androidx.media.filterpacks.numeric;
+
+import android.util.Log;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.geometry.Quad;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Get the sample mean and variance of a 2-D buffer of bytes over a given rectangle.
+ * TODO: Add more statistics as needed.
+ * TODO: Check if crop rectangle is necessary to be included in this filter.
+ */
+public class StatsFilter extends Filter {
+
+    private static final int MEAN_INDEX = 0;
+    private static final int STDEV_INDEX = 1;
+
+    private final float[] mStats = new float[2];
+
+    private Quad mCropRect = Quad.fromRect(0f, 0f, 1f, 1f);
+    private static final String TAG = "StatsFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /**
+     * @param context
+     * @param name
+     */
+    public StatsFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType inputFrame = FrameType.buffer2D(FrameType.ELEMENT_INT8);
+        FrameType floatT = FrameType.single(float.class);
+        return new Signature()
+                .addInputPort("buffer", Signature.PORT_REQUIRED, inputFrame)
+                .addInputPort("cropRect", Signature.PORT_OPTIONAL, FrameType.single(Quad.class))
+                .addOutputPort("mean", Signature.PORT_REQUIRED, floatT)
+                .addOutputPort("stdev", Signature.PORT_REQUIRED, floatT)
+                .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("cropRect")) {
+            port.bindToFieldNamed("mCropRect");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    private void calcMeanAndStd(ByteBuffer pixelBuffer, int width, int height, Quad quad) {
+        // Native
+        pixelBuffer.rewind();
+        regionscore(pixelBuffer, width, height, quad.topLeft().x, quad.topLeft().y,
+                quad.bottomRight().x, quad.bottomRight().y, mStats);
+        if (mLogVerbose) {
+            Log.v(TAG, "Native calc stats: Mean = " + mStats[MEAN_INDEX] + ", Stdev = "
+                    + mStats[STDEV_INDEX]);
+        }
+    }
+
+    /**
+     * @see androidx.media.filterfw.Filter#onProcess()
+     */
+    @Override
+    protected void onProcess() {
+        FrameBuffer2D inputFrame = getConnectedInputPort("buffer").pullFrame().asFrameImage2D();
+        ByteBuffer pixelBuffer = inputFrame.lockBytes(Frame.MODE_READ);
+
+        calcMeanAndStd(pixelBuffer, inputFrame.getWidth(), inputFrame.getHeight(), mCropRect);
+        inputFrame.unlock();
+
+        OutputPort outPort = getConnectedOutputPort("mean");
+        FrameValue outFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        outFrame.setValue(mStats[MEAN_INDEX]);
+        outPort.pushFrame(outFrame);
+
+        OutputPort outPortStdev = getConnectedOutputPort("stdev");
+        FrameValue outFrameStdev = outPortStdev.fetchAvailableFrame(null).asFrameValue();
+        outFrameStdev.setValue(mStats[STDEV_INDEX]);
+        outPortStdev.pushFrame(outFrameStdev);
+    }
+
+    private native void regionscore(ByteBuffer imageBuffer, int width, int height, float left,
+            float top, float right, float bottom, float[] statsArray);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SurfaceHolderTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SurfaceHolderTarget.java
new file mode 100644
index 0000000..dac723b
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/SurfaceHolderTarget.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 androidx.media.filterpacks.image;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.RenderTarget;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.ViewFilter;
+
+public class SurfaceHolderTarget extends ViewFilter {
+
+    private SurfaceHolder mSurfaceHolder = null;
+    private RenderTarget mRenderTarget = null;
+    private ImageShader mShader = null;
+    private boolean mHasSurface = false;
+
+    private SurfaceHolder.Callback mSurfaceHolderListener = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            // This just makes sure the holder is still the one we expect.
+            onSurfaceCreated(holder);
+        }
+
+        @Override
+        public void surfaceCreated (SurfaceHolder holder) {
+            onSurfaceCreated(holder);
+        }
+
+        @Override
+        public void surfaceDestroyed (SurfaceHolder holder) {
+            onDestroySurface();
+        }
+    };
+
+    public SurfaceHolderTarget(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public void onBindToView(View view) {
+        if (view instanceof SurfaceView) {
+            SurfaceHolder holder = ((SurfaceView)view).getHolder();
+            if (holder == null) {
+                throw new RuntimeException("Could not get SurfaceHolder from SurfaceView "
+                    + view + "!");
+            }
+            setSurfaceHolder(holder);
+        } else {
+            throw new IllegalArgumentException("View must be a SurfaceView!");
+        }
+    }
+
+    public void setSurfaceHolder(SurfaceHolder holder) {
+        if (isRunning()) {
+            throw new IllegalStateException("Cannot set SurfaceHolder while running!");
+        }
+        mSurfaceHolder = holder;
+    }
+
+    public synchronized void onDestroySurface() {
+        if (mRenderTarget != null) {
+            mRenderTarget.release();
+            mRenderTarget = null;
+        }
+        mHasSurface = false;
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageType = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        return super.getSignature()
+            .addInputPort("image", Signature.PORT_REQUIRED, imageType)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onInputPortOpen(InputPort port) {
+        super.connectViewInputs(port);
+    }
+
+    @Override
+    protected synchronized void onPrepare() {
+        if (isOpenGLSupported()) {
+            mShader = ImageShader.createIdentity();
+        }
+    }
+
+    @Override
+    protected synchronized void onOpen() {
+        mSurfaceHolder.addCallback(mSurfaceHolderListener);
+        Surface surface = mSurfaceHolder.getSurface();
+        mHasSurface = (surface != null) && surface.isValid();
+    }
+
+    @Override
+    protected synchronized void onProcess() {
+        FrameImage2D image = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        if (mHasSurface) {
+            // Synchronize the surface holder in case another filter is accessing this surface.
+            synchronized (mSurfaceHolder) {
+                if (isOpenGLSupported()) {
+                    renderGL(image);
+                } else {
+                    renderCanvas(image);
+                }
+            }
+        }
+    }
+
+    /**
+     * Renders the given frame to the screen using GLES2.
+     * @param image the image to render
+     */
+    private void renderGL(FrameImage2D image) {
+        if (mRenderTarget == null) {
+            mRenderTarget = RenderTarget.currentTarget().forSurfaceHolder(mSurfaceHolder);
+            mRenderTarget.registerAsDisplaySurface();
+        }
+        Rect frameRect = new Rect(0, 0, image.getWidth(), image.getHeight());
+        Rect surfRect = mSurfaceHolder.getSurfaceFrame();
+        setupShader(mShader, frameRect, surfRect);
+        mShader.process(image.lockTextureSource(),
+                        mRenderTarget,
+                        surfRect.width(),
+                        surfRect.height());
+        image.unlock();
+        mRenderTarget.swapBuffers();
+    }
+
+    /**
+     * Renders the given frame to the screen using a Canvas.
+     * @param image the image to render
+     */
+    private void renderCanvas(FrameImage2D image) {
+        Canvas canvas = mSurfaceHolder.lockCanvas();
+        Bitmap bitmap = image.toBitmap();
+        Rect sourceRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        Rect surfaceRect = mSurfaceHolder.getSurfaceFrame();
+        RectF targetRect = getTargetRect(sourceRect, surfaceRect);
+        canvas.drawColor(Color.BLACK);
+        if (targetRect.width() > 0 && targetRect.height() > 0) {
+            canvas.scale(surfaceRect.width(), surfaceRect.height());
+            canvas.drawBitmap(bitmap, sourceRect, targetRect, new Paint());
+        }
+        mSurfaceHolder.unlockCanvasAndPost(canvas);
+    }
+
+    @Override
+    protected synchronized void onClose() {
+        if (mRenderTarget != null) {
+            mRenderTarget.unregisterAsDisplaySurface();
+            mRenderTarget.release();
+            mRenderTarget = null;
+        }
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.removeCallback(mSurfaceHolderListener);
+        }
+    }
+
+    private synchronized void onSurfaceCreated(SurfaceHolder holder) {
+        if (mSurfaceHolder != holder) {
+            throw new RuntimeException("Unexpected Holder!");
+        }
+        mHasSurface = true;
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextViewTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextViewTarget.java
new file mode 100644
index 0000000..5aafced
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextViewTarget.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 androidx.media.filterpacks.text;
+
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.ViewFilter;
+
+public class TextViewTarget extends ViewFilter {
+
+    private TextView mTextView = null;
+
+    public TextViewTarget(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public void onBindToView(View view) {
+        if (view instanceof TextView) {
+            mTextView = (TextView)view;
+        } else {
+            throw new IllegalArgumentException("View must be a TextView!");
+        }
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("text", Signature.PORT_REQUIRED, FrameType.single(String.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameValue textFrame = getConnectedInputPort("text").pullFrame().asFrameValue();
+        final String text = (String)textFrame.getValue();
+        if (mTextView != null) {
+            mTextView.post(new Runnable() {
+                @Override
+                public void run() {
+                    mTextView.setText(text);
+                }
+            });
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextureSource.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextureSource.java
new file mode 100644
index 0000000..30fda82
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TextureSource.java
@@ -0,0 +1,121 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.graphics.Bitmap;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+
+import java.nio.ByteBuffer;
+
+public class TextureSource {
+
+    private int mTexId;
+    private int mTarget;
+    private boolean mIsOwner;
+    private boolean mIsAllocated = false;
+
+    public static TextureSource fromTexture(int texId, int target) {
+        return new TextureSource(texId, target, false);
+    }
+
+    public static TextureSource fromTexture(int texId) {
+        return new TextureSource(texId, GLES20.GL_TEXTURE_2D, false);
+    }
+
+    public static TextureSource newTexture() {
+        return new TextureSource(GLToolbox.generateTexture(), GLES20.GL_TEXTURE_2D, true);
+    }
+
+    public static TextureSource newExternalTexture() {
+        return new TextureSource(GLToolbox.generateTexture(),
+                                 GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                                 true);
+    }
+
+    public int getTextureId() {
+        return mTexId;
+    }
+
+    public int getTarget() {
+        return mTarget;
+    }
+
+    public void bind() {
+        GLES20.glBindTexture(mTarget, mTexId);
+        GLToolbox.checkGlError("glBindTexture");
+    }
+
+    public void allocate(int width, int height) {
+        //Log.i("TextureSource", "Allocating empty texture " + mTexId + ": " + width + "x" + height + ".");
+        GLToolbox.allocateTexturePixels(mTexId, mTarget, width, height);
+        mIsAllocated = true;
+    }
+
+    public void allocateWithPixels(ByteBuffer pixels, int width, int height) {
+        //Log.i("TextureSource", "Uploading pixels to texture " + mTexId + ": " + width + "x" + height + ".");
+        GLToolbox.setTexturePixels(mTexId, mTarget, pixels, width, height);
+        mIsAllocated = true;
+    }
+
+    public void allocateWithBitmapPixels(Bitmap bitmap) {
+        //Log.i("TextureSource", "Uploading pixels to texture " + mTexId + "!");
+        GLToolbox.setTexturePixels(mTexId, mTarget, bitmap);
+        mIsAllocated = true;
+    }
+
+    public void generateMipmaps() {
+        GLES20.glBindTexture(mTarget, mTexId);
+        GLES20.glTexParameteri(mTarget,
+                               GLES20.GL_TEXTURE_MIN_FILTER,
+                               GLES20.GL_LINEAR_MIPMAP_LINEAR);
+        GLES20.glGenerateMipmap(mTarget);
+        GLES20.glBindTexture(mTarget, 0);
+    }
+
+    public void setParameter(int parameter, int value) {
+        GLES20.glBindTexture(mTarget, mTexId);
+        GLES20.glTexParameteri(mTarget, parameter, value);
+        GLES20.glBindTexture(mTarget, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public void release() {
+        if (GLToolbox.isTexture(mTexId) && mIsOwner) {
+            GLToolbox.deleteTexture(mTexId);
+        }
+        mTexId = GLToolbox.textureNone();
+    }
+
+    @Override
+    public String toString() {
+        return "TextureSource(id=" + mTexId + ", target=" + mTarget + ")";
+    }
+
+    boolean isAllocated() {
+        return mIsAllocated;
+    }
+
+    private TextureSource(int texId, int target, boolean isOwner) {
+        mTexId = texId;
+        mTarget = target;
+        mIsOwner = isOwner;
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Throughput.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Throughput.java
new file mode 100644
index 0000000..c16aae0
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/Throughput.java
@@ -0,0 +1,52 @@
+/*
+ * 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 androidx.media.filterpacks.performance;
+
+public class Throughput {
+
+    private final int mTotalFrames;
+    private final int mPeriodFrames;
+    private final long mPeriodTime;
+    
+    public Throughput(int totalFrames, int periodFrames, long periodTime, int size) {
+        mTotalFrames = totalFrames;
+        mPeriodFrames = periodFrames;
+        mPeriodTime = periodTime;
+    }
+
+    public int getTotalFrameCount() {
+        return mTotalFrames;
+    }
+
+    public int getPeriodFrameCount() {
+        return mPeriodFrames;
+    }
+
+    public long getPeriodTime() {
+        return mPeriodTime;
+    }
+
+    public float getFramesPerSecond() {
+        return mPeriodFrames / (mPeriodTime / 1000.0f);
+    }
+
+    @Override
+    public String toString() {
+        return Math.round(getFramesPerSecond()) + " FPS";
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ThroughputFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ThroughputFilter.java
new file mode 100644
index 0000000..25243a7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ThroughputFilter.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 androidx.media.filterpacks.performance;
+
+import android.util.Log;
+import android.os.SystemClock;
+
+import androidx.media.filterfw.*;
+
+public class ThroughputFilter extends Filter {
+
+    private int mPeriod = 3;
+    private long mLastTime = 0;
+    private int mTotalFrameCount = 0;
+    private int mPeriodFrameCount = 0;
+
+    public ThroughputFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+
+    @Override
+    public Signature getSignature() {
+        FrameType throughputType = FrameType.single(Throughput.class);
+        return new Signature()
+            .addInputPort("frame", Signature.PORT_REQUIRED, FrameType.any())
+            .addOutputPort("throughput", Signature.PORT_REQUIRED, throughputType)
+            .addOutputPort("frame", Signature.PORT_REQUIRED, FrameType.any())
+            .addInputPort("period", Signature.PORT_OPTIONAL, FrameType.single(int.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("period")) {
+            port.bindToFieldNamed("mPeriod");
+        } else {
+            port.attachToOutputPort(getConnectedOutputPort("frame"));
+        }
+    }
+
+    @Override
+    protected void onOpen() {
+        mTotalFrameCount = 0;
+        mPeriodFrameCount = 0;
+        mLastTime = 0;
+    }
+
+    @Override
+    protected synchronized void onProcess() {
+        Frame inputFrame = getConnectedInputPort("frame").pullFrame();
+
+        // 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)) {
+            Log.i("Thru", "It is time!");
+            OutputPort tpPort = getConnectedOutputPort("throughput");
+            Throughput throughput = new Throughput(mTotalFrameCount,
+                                                   mPeriodFrameCount,
+                                                   curTime - mLastTime,
+                                                   inputFrame.getElementCount());
+            FrameValue throughputFrame = tpPort.fetchAvailableFrame(null).asFrameValue();
+            throughputFrame.setValue(throughput);
+            tpPort.pushFrame(throughputFrame);
+            mLastTime = curTime;
+            mPeriodFrameCount = 0;
+        }
+
+        getConnectedOutputPort("frame").pushFrame(inputFrame);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToGrayValuesFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToGrayValuesFilter.java
new file mode 100644
index 0000000..8e0fd6c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToGrayValuesFilter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterpacks.image;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.RenderTarget;
+import androidx.media.filterfw.Signature;
+import androidx.media.filterfw.geometry.Quad;
+
+import java.nio.ByteBuffer;
+
+public class ToGrayValuesFilter extends Filter {
+
+    private final static String mGrayPackFragment =
+        "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" +
+        // Here is an example showing how this works:
+        // Assuming the input texture is 1x4 while the output texture is 1x1
+        // the coordinates of the 4 input pixels will be:
+        // { (0.125, 0.5), (0.375, 0.5), (0.625, 0.5), (0.875, 0.5) }
+        // and the coordinates of the 1 output pixels will be:
+        // { (0.5, 0.5) }
+        // the equation below locates the 4 input pixels from the coordinate of the output pixel
+        "    vec4 p = texture2D(tex_sampler_0,\n" +
+        "                       v_texcoord + vec2(pix_stride * (float(i) - 1.5), 0.0));\n" +
+        "    gl_FragColor[i] = dot(p, coeff_y);\n" +
+        "  }\n" +
+        "}\n";
+
+    private ImageShader mShader;
+
+    private FrameType mImageInType;
+
+    public ToGrayValuesFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        mImageInType = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.buffer2D(FrameType.ELEMENT_INT8);
+        return new Signature()
+            .addInputPort("image", Signature.PORT_REQUIRED, mImageInType)
+            .addOutputPort("image", Signature.PORT_REQUIRED, imageOut)
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onPrepare() {
+        if (isOpenGLSupported()) {
+            mShader = new ImageShader(mGrayPackFragment);
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("image");
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        int[] dim = inputImage.getDimensions();
+        FrameBuffer2D outputFrame;
+        ByteBuffer grayBuffer;
+
+        if (isOpenGLSupported()) {
+            // crop out the portion of inputImage that will be used to generate outputFrame.
+            int modular = dim[0] % 4;
+            int[] outDim = new int[] {dim[0] - modular, dim[1]};
+            outputFrame = outPort.fetchAvailableFrame(outDim).asFrameBuffer2D();
+            grayBuffer = outputFrame.lockBytes(Frame.MODE_WRITE);
+
+            int[] targetDims = new int[] { outDim[0] / 4, outDim[1] };
+            FrameImage2D targetFrame = Frame.create(mImageInType, targetDims).asFrameImage2D();
+            mShader.setSourceQuad(Quad.fromRect(0f, 0f, ((float)outDim[0])/dim[0], 1f));
+            mShader.setUniformValue("pix_stride", 1f / outDim[0]);
+            mShader.process(inputImage, targetFrame);
+            RenderTarget grayTarget = targetFrame.lockRenderTarget();
+            grayTarget.readPixelData(grayBuffer, targetDims[0], targetDims[1]);
+            targetFrame.unlock();
+            targetFrame.release();
+        } else {
+            outputFrame = outPort.fetchAvailableFrame(dim).asFrameBuffer2D();
+            grayBuffer = outputFrame.lockBytes(Frame.MODE_WRITE);
+            ByteBuffer inputBuffer  = inputImage.lockBytes(Frame.MODE_READ);
+            if (!toGrayValues(inputBuffer, grayBuffer)) {
+                throw new RuntimeException(
+                        "Native implementation encountered an error during processing!");
+            }
+            inputImage.unlock();
+        }
+        outputFrame.unlock();
+        outPort.pushFrame(outputFrame);
+    }
+
+    private static native boolean toGrayValues(ByteBuffer imageBuffer, ByteBuffer grayBuffer);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToStringFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToStringFilter.java
new file mode 100644
index 0000000..7306b61
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ToStringFilter.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 androidx.media.filterpacks.text;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public class ToStringFilter extends Filter {
+
+    public ToStringFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("object", Signature.PORT_REQUIRED, FrameType.single())
+            .addOutputPort("string", Signature.PORT_REQUIRED, FrameType.single(String.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameValue objectFrame = getConnectedInputPort("object").pullFrame().asFrameValue();
+        String outStr = objectFrame.getValue().toString();
+        OutputPort outPort = getConnectedOutputPort("string");
+        FrameValue stringFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        stringFrame.setValue(outStr);
+        outPort.pushFrame(stringFrame);
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TransformUtils.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TransformUtils.java
new file mode 100644
index 0000000..8dd1949
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/TransformUtils.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 androidx.media.filterpacks.transform;
+
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.TextureSource;
+
+import java.util.Arrays;
+
+/** Internal class that contains utility functions used by the transform filters. **/
+class TransformUtils {
+
+    public static int powOf2(int x) {
+        --x;
+        // Fill with 1s
+        x |= x >> 1;
+        x |= x >> 2;
+        x |= x >> 4;
+        x |= x >> 8;
+        x |= x >> 16;
+        // Next int is now pow-of-2
+        return x + 1;
+    }
+
+    public static FrameImage2D makeMipMappedFrame(FrameImage2D current, int[] dimensions) {
+        // Note: Future versions of GLES will support NPOT mipmapping. When these become more
+        // widely used, we can add a check here to disable frame expansion on such devices.
+        int[] pow2Dims = new int[] { powOf2(dimensions[0]), powOf2(dimensions[1]) };
+        if (current == null) {
+            FrameType imageType = FrameType.image2D(FrameType.ELEMENT_RGBA8888,
+                                                    FrameType.READ_GPU | FrameType.WRITE_GPU);
+            current = Frame.create(imageType, pow2Dims).asFrameImage2D();
+        } else if (!Arrays.equals(dimensions, current.getDimensions())) {
+            current.resize(pow2Dims);
+        }
+        return current;
+    }
+
+    public static FrameImage2D makeTempFrame(FrameImage2D current, int[] dimensions) {
+        if (current == null) {
+            FrameType imageType = FrameType.image2D(FrameType.ELEMENT_RGBA8888,
+                                                    FrameType.READ_GPU | FrameType.WRITE_GPU);
+            current = Frame.create(imageType, dimensions).asFrameImage2D();
+        } else if (!Arrays.equals(dimensions, current.getDimensions())) {
+            current.resize(dimensions);
+        }
+        return current;
+    }
+
+    public static void generateMipMaps(FrameImage2D frame) {
+        TextureSource texture = frame.lockTextureSource();
+        texture.generateMipmaps();
+        frame.unlock();
+    }
+
+    public static void setTextureParameter(FrameImage2D frame, int param, int value) {
+        TextureSource texture = frame.lockTextureSource();
+        texture.setParameter(param, value);
+        frame.unlock();
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ValueTarget.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ValueTarget.java
new file mode 100644
index 0000000..e2db8af
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ValueTarget.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.media.filterpacks.base;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.media.filterfw.*;
+
+public final class ValueTarget extends Filter {
+
+    public static interface ValueListener {
+        public void onReceivedValue(Object value);
+    }
+
+    private ValueListener mListener = null;
+    private Handler mHandler = null;
+
+    public ValueTarget(MffContext context, String name) {
+        super(context, name);
+    }
+
+    public void setListener(ValueListener listener, boolean onCallerThread) {
+        if (isRunning()) {
+            throw new IllegalStateException("Attempting to bind filter to callback while it is "
+                + "running!");
+        }
+        mListener = listener;
+        if (onCallerThread) {
+            if (Looper.myLooper() == null) {
+                throw new IllegalArgumentException("Attempting to set callback on thread which "
+                    + "has no looper!");
+            }
+            mHandler = new Handler();
+        }
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("value", Signature.PORT_REQUIRED, FrameType.single())
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameValue valueFrame = getConnectedInputPort("value").pullFrame().asFrameValue();
+        if (mListener != null) {
+            if (mHandler != null) {
+                postValueToUiThread(valueFrame.getValue());
+            } else {
+                mListener.onReceivedValue(valueFrame.getValue());
+            }
+        }
+    }
+
+    private void postValueToUiThread(final Object value) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onReceivedValue(value);
+            }
+        });
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/VariableSource.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/VariableSource.java
new file mode 100644
index 0000000..69060cb
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/VariableSource.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 androidx.media.filterpacks.base;
+
+import androidx.media.filterfw.*;
+
+// TODO: Rename back to ValueSource? Seems to make more sense even if we use it as a Variable
+// in some contexts.
+public final class VariableSource extends Filter {
+
+    private Object mValue = null;
+    private OutputPort mOutputPort = null;
+
+    public VariableSource(MffContext context, String name) {
+        super(context, name);
+    }
+
+    public synchronized void setValue(Object value) {
+        mValue = value;
+    }
+
+    public synchronized Object getValue() {
+        return mValue;
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addOutputPort("value", Signature.PORT_REQUIRED, FrameType.single())
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onPrepare() {
+        mOutputPort = getConnectedOutputPort("value");
+    }
+
+    @Override
+    protected synchronized void onProcess() {
+        FrameValue frame = mOutputPort.fetchAvailableFrame(null).asFrameValue();
+        frame.setValue(mValue);
+        mOutputPort.pushFrame(frame);
+    }
+
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ViewFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ViewFilter.java
new file mode 100644
index 0000000..ddb7222
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/ViewFilter.java
@@ -0,0 +1,159 @@
+/*
+ * 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 androidx.media.filterfw;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.View;
+
+/**
+ * TODO: Move this to filterpacks/base?
+ */
+public abstract class ViewFilter extends Filter {
+
+    public static final int SCALE_STRETCH = 1;
+    public static final int SCALE_FIT = 2;
+    public static final int SCALE_FILL = 3;
+
+    protected int mScaleMode = SCALE_FIT;
+    protected float[] mClearColor = new float[] { 0f, 0f, 0f, 1f };
+    protected boolean mFlipVertically = true;
+
+    private String mRequestedScaleMode = null;
+
+    protected ViewFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    /**
+     * Binds the filter to a view.
+     * View filters support visualizing data to a view. Check the specific filter documentation
+     * for details. The view may be bound only if the filter's graph is not running.
+     *
+     * @param view the view to bind to.
+     * @throws IllegalStateException if the method is called while the graph is running.
+     */
+    public void bindToView(View view) {
+        if (isRunning()) {
+            throw new IllegalStateException("Attempting to bind filter to view while it is "
+                + "running!");
+        }
+        onBindToView(view);
+    }
+
+    public void setScaleMode(int scaleMode) {
+        if (isRunning()) {
+            throw new IllegalStateException("Attempting to change scale mode while filter is "
+                + "running!");
+        }
+        mScaleMode = scaleMode;
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addInputPort("scaleMode", Signature.PORT_OPTIONAL, FrameType.single(String.class))
+            .addInputPort("flip", Signature.PORT_OPTIONAL, FrameType.single(boolean.class));
+    }
+
+    /**
+     * Subclasses must override this method to bind their filter to the specified view.
+     *
+     * When this method is called, Filter implementations may assume that the graph is not
+     * currently running.
+     */
+    protected abstract void onBindToView(View view);
+
+    /**
+     * TODO: Document.
+     */
+    protected RectF getTargetRect(Rect frameRect, Rect bufferRect) {
+        RectF result = new RectF();
+        if (bufferRect.width() > 0 && bufferRect.height() > 0) {
+            float frameAR = (float)frameRect.width() / frameRect.height();
+            float bufferAR = (float)bufferRect.width() / bufferRect.height();
+            float relativeAR = bufferAR / frameAR;
+            switch (mScaleMode) {
+                case SCALE_STRETCH:
+                    result.set(0f, 0f, 1f, 1f);
+                    break;
+                case SCALE_FIT:
+                    if (relativeAR > 1.0f) {
+                        float x = 0.5f - 0.5f / relativeAR;
+                        float y = 0.0f;
+                        result.set(x, y, x + 1.0f / relativeAR, y + 1.0f);
+                    } else {
+                        float x = 0.0f;
+                        float y = 0.5f - 0.5f * relativeAR;
+                        result.set(x, y, x + 1.0f, y + relativeAR);
+                    }
+                    break;
+                case SCALE_FILL:
+                    if (relativeAR > 1.0f) {
+                        float x = 0.0f;
+                        float y = 0.5f - 0.5f * relativeAR;
+                        result.set(x, y, x + 1.0f, y + relativeAR);
+                    } else {
+                        float x = 0.5f - 0.5f / relativeAR;
+                        float y = 0.0f;
+                        result.set(x, y, x + 1.0f / relativeAR, y + 1.0f);
+                    }
+                    break;
+            }
+        }
+        return result;
+    }
+
+    protected void connectViewInputs(InputPort port) {
+        if (port.getName().equals("scaleMode")) {
+            port.bindToListener(mScaleModeListener);
+            port.setAutoPullEnabled(true);
+        } else if (port.getName().equals("flip")) {
+            port.bindToFieldNamed("mFlipVertically");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    protected void setupShader(ImageShader shader, Rect frameRect, Rect outputRect) {
+        shader.setTargetRect(getTargetRect(frameRect, outputRect));
+        shader.setClearsOutput(true);
+        shader.setClearColor(mClearColor);
+        if (mFlipVertically) {
+            shader.setSourceRect(0f, 1f, 1f, -1f);
+        }
+    }
+
+    private InputPort.FrameListener mScaleModeListener = new InputPort.FrameListener() {
+        @Override
+        public void onFrameReceived(InputPort port, Frame frame) {
+            String scaleMode = (String)frame.asFrameValue().getValue();
+            if (!scaleMode.equals(mRequestedScaleMode)) {
+                mRequestedScaleMode = scaleMode;
+                if (scaleMode.equals("stretch")) {
+                    mScaleMode = SCALE_STRETCH;
+                } else if (scaleMode.equals("fit")) {
+                    mScaleMode = SCALE_FIT;
+                } else if (scaleMode.equals("fill")) {
+                    mScaleMode = SCALE_FILL;
+                } else {
+                    throw new RuntimeException("Unknown scale-mode '" + scaleMode + "'!");
+                }
+            }
+        }
+    };
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioSample.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioSample.java
new file mode 100644
index 0000000..c7eec26
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioSample.java
@@ -0,0 +1,31 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+public class AudioSample {
+
+    public final int sampleRate;
+    public final int channelCount;
+    public final byte[] bytes;
+
+    public AudioSample(int sampleRate, int channelCount, byte[] bytes) {
+        this.sampleRate = sampleRate;
+        this.channelCount = channelCount;
+        this.bytes = bytes;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioTrackDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioTrackDecoder.java
new file mode 100644
index 0000000..0219fd7
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/AudioTrackDecoder.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+
+import androidx.media.filterfw.FrameValue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * {@link TrackDecoder} for decoding audio tracks.
+ *
+ * TODO: find out if we always get 16 bits per channel and document.
+ */
+@TargetApi(16)
+public class AudioTrackDecoder extends TrackDecoder {
+
+    private final ByteArrayOutputStream mAudioByteStream; // Guarded by mAudioByteStreamLock.
+    private final Object mAudioByteStreamLock;
+
+    private int mAudioSampleRate;
+    private int mAudioChannelCount;
+    private long mAudioPresentationTimeUs;
+
+    public AudioTrackDecoder(int trackIndex, MediaFormat format, Listener listener) {
+        super(trackIndex, format, listener);
+
+        if (!DecoderUtil.isAudioFormat(format)) {
+            throw new IllegalArgumentException(
+                    "AudioTrackDecoder can only be used with audio formats");
+        }
+
+        mAudioByteStream = new ByteArrayOutputStream();
+        mAudioByteStreamLock = new Object();
+
+        mAudioSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+        mAudioChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+    }
+
+    @Override
+    protected MediaCodec initMediaCodec(MediaFormat format) {
+        MediaCodec mediaCodec = MediaCodec.createDecoderByType(
+                format.getString(MediaFormat.KEY_MIME));
+        mediaCodec.configure(format, null, null, 0);
+        return mediaCodec;
+    }
+
+    @Override
+    protected boolean onDataAvailable(
+            MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info) {
+        ByteBuffer buffer = buffers[bufferIndex];
+        byte[] data = new byte[info.size];
+        buffer.position(info.offset);
+        buffer.get(data, 0, info.size);
+
+        synchronized (mAudioByteStreamLock) {
+            try {
+                if (mAudioByteStream.size() == 0 && data.length > 0) {
+                    mAudioPresentationTimeUs = info.presentationTimeUs;
+                }
+
+                mAudioByteStream.write(data);
+            } catch (IOException e) {
+                // Just drop the audio sample.
+            }
+        }
+
+        buffer.clear();
+        codec.releaseOutputBuffer(bufferIndex, false);
+        notifyListener();
+        return true;
+    }
+
+    /**
+     * Fills the argument {@link FrameValue} with an audio sample containing the audio that was
+     * decoded since the last call of this method. The decoder's buffer is cleared as a result.
+     */
+    public void grabSample(FrameValue audioFrame) {
+        synchronized (mAudioByteStreamLock) {
+            if (audioFrame != null) {
+                AudioSample sample = new AudioSample(
+                        mAudioSampleRate, mAudioChannelCount, mAudioByteStream.toByteArray());
+                audioFrame.setValue(sample);
+                audioFrame.setTimestamp(mAudioPresentationTimeUs * 1000);
+            }
+            clearBuffer();
+        }
+    }
+
+    /**
+     * Clears the decoder's buffer.
+     */
+    public void clearBuffer() {
+        synchronized (mAudioByteStreamLock) {
+            mAudioByteStream.reset();
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/CpuVideoTrackDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/CpuVideoTrackDecoder.java
new file mode 100644
index 0000000..96f3059
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/CpuVideoTrackDecoder.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.util.SparseIntArray;
+import androidx.media.filterfw.ColorSpace;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.PixelUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * {@link TrackDecoder} that decodes a video track and renders the frames onto a
+ * {@link SurfaceTexture}.
+ *
+ * This implementation purely uses CPU based methods to decode and color-convert the frames.
+ */
+@TargetApi(16)
+public class CpuVideoTrackDecoder extends VideoTrackDecoder {
+
+    private static final int COLOR_FORMAT_UNSET = -1;
+
+    private final int mWidth;
+    private final int mHeight;
+
+    private int mColorFormat = COLOR_FORMAT_UNSET;
+    private long mCurrentPresentationTimeUs;
+    private ByteBuffer mDecodedBuffer;
+    private ByteBuffer mUnrotatedBytes;
+
+    protected CpuVideoTrackDecoder(int trackIndex, MediaFormat format, Listener listener) {
+        super(trackIndex, format, listener);
+
+        mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+        mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+    }
+
+    @Override
+    protected MediaCodec initMediaCodec(MediaFormat format) {
+        // Find a codec for our video that can output to one of our supported color-spaces
+        MediaCodec mediaCodec = findDecoderCodec(format, new int[] {
+                CodecCapabilities.COLOR_Format32bitARGB8888,
+                CodecCapabilities.COLOR_FormatYUV420Planar});
+        if (mediaCodec == null) {
+            throw new RuntimeException(
+                    "Could not find a suitable decoder for format: " + format + "!");
+        }
+        mediaCodec.configure(format, null, null, 0);
+        return mediaCodec;
+    }
+
+    @Override
+    protected boolean onDataAvailable(
+            MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info) {
+
+        mCurrentPresentationTimeUs = info.presentationTimeUs;
+        mDecodedBuffer = buffers[bufferIndex];
+
+        if (mColorFormat == -1) {
+            mColorFormat = codec.getOutputFormat().getInteger(MediaFormat.KEY_COLOR_FORMAT);
+        }
+
+        markFrameAvailable();
+        notifyListener();
+
+        // Wait for the grab before we release this buffer.
+        waitForFrameGrab();
+
+        codec.releaseOutputBuffer(bufferIndex, false);
+
+        return false;
+    }
+
+    @Override
+    protected void copyFrameDataTo(FrameImage2D outputVideoFrame, int rotation) {
+        // Calculate output dimensions
+        int outputWidth = mWidth;
+        int outputHeight = mHeight;
+        if (needSwapDimension(rotation)) {
+            outputWidth = mHeight;
+            outputHeight = mWidth;
+        }
+
+        // Create output frame
+        outputVideoFrame.resize(new int[] {outputWidth, outputHeight});
+        outputVideoFrame.setTimestamp(mCurrentPresentationTimeUs * 1000);
+        ByteBuffer outBytes = outputVideoFrame.lockBytes(Frame.MODE_WRITE);
+
+        // Set data
+        if (rotation == MediaDecoder.ROTATE_NONE) {
+            convertImage(mDecodedBuffer, outBytes, mColorFormat, mWidth, mHeight);
+        } else {
+            if (mUnrotatedBytes == null) {
+                mUnrotatedBytes = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
+            }
+            // TODO: This could be optimized by including the rotation in the color conversion.
+            convertImage(mDecodedBuffer, mUnrotatedBytes, mColorFormat, mWidth, mHeight);
+            copyRotate(mUnrotatedBytes, outBytes, rotation);
+        }
+        outputVideoFrame.unlock();
+    }
+
+    /**
+     * Copy the input data to the output data applying the specified rotation.
+     *
+     * @param input The input image data
+     * @param output Buffer for the output image data
+     * @param rotation The rotation to apply
+     */
+    private void copyRotate(ByteBuffer input, ByteBuffer output, int rotation) {
+        int offset;
+        int pixStride;
+        int rowStride;
+        switch (rotation) {
+            case MediaDecoder.ROTATE_NONE:
+                offset = 0;
+                pixStride = 1;
+                rowStride = mWidth;
+                break;
+            case MediaDecoder.ROTATE_90_LEFT:
+                offset = (mWidth - 1) * mHeight;
+                pixStride = -mHeight;
+                rowStride = 1;
+                break;
+            case MediaDecoder.ROTATE_90_RIGHT:
+                offset = mHeight - 1;
+                pixStride = mHeight;
+                rowStride = -1;
+                break;
+            case MediaDecoder.ROTATE_180:
+                offset = mWidth * mHeight - 1;
+                pixStride = -1;
+                rowStride = -mWidth;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported rotation " + rotation + "!");
+        }
+        PixelUtils.copyPixels(input, output, mWidth, mHeight, offset, pixStride, rowStride);
+    }
+
+    /**
+     * Looks for a codec with the specified requirements.
+     *
+     * The set of codecs will be filtered down to those that meet the following requirements:
+     * <ol>
+     *   <li>The codec is a decoder.</li>
+     *   <li>The codec can decode a video of the specified format.</li>
+     *   <li>The codec can decode to one of the specified color formats.</li>
+     * </ol>
+     * If multiple codecs are found, the one with the preferred color-format is taken. Color format
+     * preference is determined by the order of their appearance in the color format array.
+     *
+     * @param format The format the codec must decode.
+     * @param requiredColorFormats Array of target color spaces ordered by preference.
+     * @return A codec that meets the requirements, or null if no such codec was found.
+     */
+    private static MediaCodec findDecoderCodec(MediaFormat format, int[] requiredColorFormats) {
+        TreeMap<Integer, String> candidateCodecs = new TreeMap<Integer, String>();
+        SparseIntArray colorPriorities = intArrayToPriorityMap(requiredColorFormats);
+        for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
+            // Get next codec
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+
+            // Check that this is a decoder
+            if (info.isEncoder()) {
+                continue;
+            }
+
+            // Check if this codec can decode the video in question
+            String requiredType = format.getString(MediaFormat.KEY_MIME);
+            String[] supportedTypes = info.getSupportedTypes();
+            Set<String> typeSet = new HashSet<String>(Arrays.asList(supportedTypes));
+
+            // Check if it can decode to one of the required color formats
+            if (typeSet.contains(requiredType)) {
+                CodecCapabilities capabilities = info.getCapabilitiesForType(requiredType);
+                for (int supportedColorFormat : capabilities.colorFormats) {
+                    if (colorPriorities.indexOfKey(supportedColorFormat) >= 0) {
+                        int priority = colorPriorities.get(supportedColorFormat);
+                        candidateCodecs.put(priority, info.getName());
+                    }
+                }
+            }
+        }
+
+        // Pick the best codec (with the highest color priority)
+        if (candidateCodecs.isEmpty()) {
+            return null;
+        } else {
+            String bestCodec = candidateCodecs.firstEntry().getValue();
+            return MediaCodec.createByCodecName(bestCodec);
+        }
+    }
+
+    private static SparseIntArray intArrayToPriorityMap(int[] values) {
+        SparseIntArray result = new SparseIntArray();
+        for (int priority = 0; priority < values.length; ++priority) {
+            result.append(values[priority], priority);
+        }
+        return result;
+    }
+
+    private static void convertImage(
+            ByteBuffer input, ByteBuffer output, int colorFormat, int width, int height) {
+        switch (colorFormat) {
+            case CodecCapabilities.COLOR_Format32bitARGB8888:
+                ColorSpace.convertArgb8888ToRgba8888(input, output, width, height);
+                break;
+            case CodecCapabilities.COLOR_FormatYUV420Planar:
+                ColorSpace.convertYuv420pToRgba8888(input, output, width, height);
+                break;
+            default:
+                throw new RuntimeException("Unsupported color format: " + colorFormat + "!");
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/DecoderUtil.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/DecoderUtil.java
new file mode 100644
index 0000000..ec0ead0
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/DecoderUtil.java
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+
+@TargetApi(16)
+public class DecoderUtil {
+
+    public static boolean isAudioFormat(MediaFormat format) {
+        return format.getString(MediaFormat.KEY_MIME).startsWith("audio/");
+    }
+
+    public static boolean isVideoFormat(MediaFormat format) {
+        return format.getString(MediaFormat.KEY_MIME).startsWith("video/");
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/GpuVideoTrackDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/GpuVideoTrackDecoder.java
new file mode 100644
index 0000000..bbba9d8
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/GpuVideoTrackDecoder.java
@@ -0,0 +1,210 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.graphics.SurfaceTexture.OnFrameAvailableListener;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+import android.view.Surface;
+
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.ImageShader;
+import androidx.media.filterfw.TextureSource;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link TrackDecoder} that decodes a video track and renders the frames onto a
+ * {@link SurfaceTexture}.
+ *
+ * This implementation uses the GPU for image operations such as copying
+ * and color-space conversion.
+ */
+@TargetApi(16)
+public class GpuVideoTrackDecoder extends VideoTrackDecoder {
+
+    /**
+     * Identity fragment shader for external textures.
+     */
+    private static final String COPY_FRAGMENT_SHADER =
+            "#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 TextureSource mTextureSource;
+    private final SurfaceTexture mSurfaceTexture; // Access guarded by mFrameMonitor.
+    private final float[] mTransformMatrix;
+
+    private final int mOutputWidth;
+    private final int mOutputHeight;
+
+    private ImageShader mImageShader;
+
+    private long mCurrentPresentationTimeUs;
+
+    public GpuVideoTrackDecoder(
+            int trackIndex, MediaFormat format, Listener listener) {
+        super(trackIndex, format, listener);
+
+        // Create a surface texture to be used by the video track decoder.
+        mTextureSource = TextureSource.newExternalTexture();
+        mSurfaceTexture = new SurfaceTexture(mTextureSource.getTextureId());
+        mSurfaceTexture.detachFromGLContext();
+        mSurfaceTexture.setOnFrameAvailableListener(new OnFrameAvailableListener() {
+            @Override
+            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                markFrameAvailable();
+            }
+        });
+
+        mOutputWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+        mOutputHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+
+        mTransformMatrix = new float[16];
+    }
+
+    @Override
+    protected MediaCodec initMediaCodec(MediaFormat format) {
+        Surface surface = new Surface(mSurfaceTexture);
+        MediaCodec mediaCodec = MediaCodec.createDecoderByType(
+                format.getString(MediaFormat.KEY_MIME));
+        mediaCodec.configure(format, surface, null, 0);
+        surface.release();
+        return mediaCodec;
+    }
+
+    @Override
+    protected boolean onDataAvailable(
+            MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info) {
+        boolean textureAvailable = waitForFrameGrab();
+
+        mCurrentPresentationTimeUs = info.presentationTimeUs;
+
+        // Only render the next frame if we weren't interrupted.
+        codec.releaseOutputBuffer(bufferIndex, textureAvailable);
+
+        if (textureAvailable) {
+            if (updateTexture()) {
+                notifyListener();
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Waits for the texture's {@link OnFrameAvailableListener} to be notified and then updates
+     * the internal {@link SurfaceTexture}.
+     */
+    private boolean updateTexture() {
+        // Wait for the frame we just released to appear in the texture.
+        synchronized (mFrameMonitor) {
+            try {
+                while (!mFrameAvailable) {
+                    mFrameMonitor.wait();
+                }
+                mSurfaceTexture.attachToGLContext(mTextureSource.getTextureId());
+                mSurfaceTexture.updateTexImage();
+                mSurfaceTexture.detachFromGLContext();
+                return true;
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+
+    @Override
+    protected void copyFrameDataTo(FrameImage2D outputVideoFrame, int rotation) {
+        TextureSource targetTexture = TextureSource.newExternalTexture();
+        mSurfaceTexture.attachToGLContext(targetTexture.getTextureId());
+        mSurfaceTexture.getTransformMatrix(mTransformMatrix);
+
+        ImageShader imageShader = getImageShader();
+        imageShader.setSourceTransform(mTransformMatrix);
+
+        int outputWidth = mOutputWidth;
+        int outputHeight = mOutputHeight;
+        if (rotation != 0) {
+            float[] targetCoords = getRotationCoords(rotation);
+            imageShader.setTargetCoords(targetCoords);
+            if (needSwapDimension(rotation)) {
+                outputWidth = mOutputHeight;
+                outputHeight = mOutputWidth;
+            }
+        }
+        outputVideoFrame.resize(new int[] { outputWidth, outputHeight });
+        imageShader.process(
+                targetTexture,
+                outputVideoFrame.lockRenderTarget(),
+                outputWidth,
+                outputHeight);
+        outputVideoFrame.setTimestamp(mCurrentPresentationTimeUs * 1000);
+        outputVideoFrame.unlock();
+        targetTexture.release();
+
+        mSurfaceTexture.detachFromGLContext();
+    }
+
+    @Override
+    public void release() {
+        super.release();
+        synchronized (mFrameMonitor) {
+            mTextureSource.release();
+            mSurfaceTexture.release();
+        }
+    }
+
+    /*
+     * This method has to be called on the MFF processing thread.
+     */
+    private ImageShader getImageShader() {
+        if (mImageShader == null) {
+            mImageShader = new ImageShader(COPY_FRAGMENT_SHADER);
+            mImageShader.setTargetRect(0f, 1f, 1f, -1f);
+        }
+        return mImageShader;
+    }
+
+    /**
+     * Get the quad coords for rotation.
+     * @param rotation applied to the frame, value is one of
+     *   {ROTATE_NONE, ROTATE_90_RIGHT, ROTATE_180, ROTATE_90_LEFT}
+     * @return coords the calculated quad coords for the given rotation
+     */
+    private static float[] getRotationCoords(int rotation) {
+         switch(rotation) {
+             case MediaDecoder.ROTATE_90_RIGHT:
+                 return new float[] { 0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f };
+             case MediaDecoder.ROTATE_180:
+                 return new float[] { 1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f };
+             case MediaDecoder.ROTATE_90_LEFT:
+                 return new float[] { 1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f };
+             case MediaDecoder.ROTATE_NONE:
+                 return new float[] { 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f };
+             default:
+                 throw new IllegalArgumentException("Unsupported rotation angle.");
+         }
+     }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java
new file mode 100644
index 0000000..aa57394
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java
@@ -0,0 +1,426 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.RenderTarget;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+@TargetApi(16)
+public class MediaDecoder implements
+        Runnable,
+        TrackDecoder.Listener {
+
+    public interface Listener {
+        /**
+         * Notifies a listener when a decoded video frame is available. The listener should use
+         * {@link MediaDecoder#grabVideoFrame(FrameImage2D, int)} to grab the video data for this
+         * frame.
+         */
+        void onVideoFrameAvailable();
+
+        /**
+         * Notifies a listener when one or more audio samples are available. The listener should use
+         * {@link MediaDecoder#grabAudioSamples(FrameValue)} to grab the audio samples.
+         */
+        void onAudioSamplesAvailable();
+
+        /**
+         * Notifies a listener that decoding has started. This method is called on the decoder
+         * thread.
+         */
+        void onDecodingStarted();
+
+        /**
+         * Notifies a listener that decoding has stopped. This method is called on the decoder
+         * thread.
+         */
+        void onDecodingStopped();
+
+        /**
+         * Notifies a listener that an error occurred. If an error occurs, {@link MediaDecoder} is
+         * stopped and no more events are reported to this {@link Listener}'s callbacks.
+         * This method is called on the decoder thread.
+         */
+        void onError(Exception e);
+    }
+
+    public static final int ROTATE_NONE = 0;
+    public static final int ROTATE_90_RIGHT = 90;
+    public static final int ROTATE_180 = 180;
+    public static final int ROTATE_90_LEFT = 270;
+
+    private static final String LOG_TAG = "MediaDecoder";
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_EVENTS = 32;
+    private static final int EVENT_START = 0;
+    private static final int EVENT_STOP = 1;
+    private static final int EVENT_EOF = 2;
+
+    private final Listener mListener;
+    private final Uri mUri;
+    private final Context mContext;
+
+    private final LinkedBlockingQueue<Integer> mEventQueue;
+
+    private final Thread mDecoderThread;
+
+    private MediaExtractor mMediaExtractor;
+
+    private RenderTarget mRenderTarget;
+
+    private int mDefaultRotation;
+    private int mVideoTrackIndex;
+    private int mAudioTrackIndex;
+
+    private VideoTrackDecoder mVideoTrackDecoder;
+    private AudioTrackDecoder mAudioTrackDecoder;
+
+    private boolean mStarted;
+
+    private long mStartMicros;
+
+    private boolean mOpenGLEnabled = true;
+
+    private boolean mSignaledEndOfInput;
+    private boolean mSeenEndOfAudioOutput;
+    private boolean mSeenEndOfVideoOutput;
+
+    public MediaDecoder(Context context, Uri uri, Listener listener) {
+        this(context, uri, 0, listener);
+    }
+
+    public MediaDecoder(Context context, Uri uri, long startMicros, Listener listener) {
+        if (context == null) {
+            throw new NullPointerException("context cannot be null");
+        }
+        mContext = context;
+
+        if (uri == null) {
+            throw new NullPointerException("uri cannot be null");
+        }
+        mUri = uri;
+
+        if (startMicros < 0) {
+            throw new IllegalArgumentException("startMicros cannot be negative");
+        }
+        mStartMicros = startMicros;
+
+        if (listener == null) {
+            throw new NullPointerException("listener cannot be null");
+        }
+        mListener = listener;
+
+        mEventQueue = new LinkedBlockingQueue<Integer>(MAX_EVENTS);
+        mDecoderThread = new Thread(this);
+    }
+
+    /**
+     * Set whether decoder may use OpenGL for decoding.
+     *
+     * This must be called before {@link #start()}.
+     *
+     * @param enabled flag whether to enable OpenGL decoding (default is true).
+     */
+    public void setOpenGLEnabled(boolean enabled) {
+        // If event-queue already has events, we have started already.
+        if (mEventQueue.isEmpty()) {
+            mOpenGLEnabled = enabled;
+        } else {
+            throw new IllegalStateException(
+                    "Must call setOpenGLEnabled() before calling start()!");
+        }
+    }
+
+    /**
+     * Returns whether OpenGL is enabled for decoding.
+     *
+     * @return whether OpenGL is enabled for decoding.
+     */
+    public boolean isOpenGLEnabled() {
+        return mOpenGLEnabled;
+    }
+
+    public void start() {
+        mEventQueue.offer(EVENT_START);
+        mDecoderThread.start();
+    }
+
+    public void stop() {
+        stop(true);
+    }
+
+    private void stop(boolean manual) {
+        if (manual) {
+            mEventQueue.offer(EVENT_STOP);
+            mDecoderThread.interrupt();
+        } else {
+            mEventQueue.offer(EVENT_EOF);
+        }
+    }
+
+    @Override
+    public void run() {
+        Integer event;
+        try {
+            while (true) {
+                event = mEventQueue.poll();
+                boolean shouldStop = false;
+                if (event != null) {
+                    switch (event) {
+                        case EVENT_START:
+                            onStart();
+                            break;
+                        case EVENT_EOF:
+                            if (mVideoTrackDecoder != null) {
+                                mVideoTrackDecoder.waitForFrameGrab();
+                            }
+                            // once the last frame has been grabbed, fall through and stop
+                        case EVENT_STOP:
+                            onStop(true);
+                            shouldStop = true;
+                            break;
+                    }
+                } else if (mStarted) {
+                    decode();
+                }
+                if (shouldStop) {
+                    break;
+                }
+
+            }
+        } catch (Exception e) {
+            mListener.onError(e);
+            onStop(false);
+        }
+    }
+
+    private void onStart() throws Exception {
+        if (mOpenGLEnabled) {
+            getRenderTarget().focus();
+        }
+
+        mMediaExtractor = new MediaExtractor();
+        mMediaExtractor.setDataSource(mContext, mUri, null);
+
+        mVideoTrackIndex = -1;
+        mAudioTrackIndex = -1;
+
+        for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
+            MediaFormat format = mMediaExtractor.getTrackFormat(i);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Uri " + mUri + ", track " + i + ": " + format);
+            }
+            if (DecoderUtil.isVideoFormat(format) && mVideoTrackIndex == -1) {
+                mVideoTrackIndex = i;
+            } else if (DecoderUtil.isAudioFormat(format) && mAudioTrackIndex == -1) {
+                mAudioTrackIndex = i;
+            }
+        }
+
+        if (mVideoTrackIndex == -1 && mAudioTrackIndex == -1) {
+            throw new IllegalArgumentException(
+                    "Couldn't find a video or audio track in the provided file");
+        }
+
+        if (mVideoTrackIndex != -1) {
+            MediaFormat videoFormat = mMediaExtractor.getTrackFormat(mVideoTrackIndex);
+            mVideoTrackDecoder = mOpenGLEnabled
+                    ? new GpuVideoTrackDecoder(mVideoTrackIndex, videoFormat, this)
+                    : new CpuVideoTrackDecoder(mVideoTrackIndex, videoFormat, this);
+            mVideoTrackDecoder.init();
+            mMediaExtractor.selectTrack(mVideoTrackIndex);
+            if (Build.VERSION.SDK_INT >= 17) {
+                retrieveDefaultRotation();
+            }
+        }
+
+        if (mAudioTrackIndex != -1) {
+            MediaFormat audioFormat = mMediaExtractor.getTrackFormat(mAudioTrackIndex);
+            mAudioTrackDecoder = new AudioTrackDecoder(mAudioTrackIndex, audioFormat, this);
+            mAudioTrackDecoder.init();
+            mMediaExtractor.selectTrack(mAudioTrackIndex);
+        }
+
+        if (mStartMicros > 0) {
+            mMediaExtractor.seekTo(mStartMicros, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+        }
+
+        mStarted = true;
+        mListener.onDecodingStarted();
+    }
+
+    @TargetApi(17)
+    private void retrieveDefaultRotation() {
+        MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+        metadataRetriever.setDataSource(mContext, mUri);
+        String rotationString = metadataRetriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+        mDefaultRotation = rotationString == null ? 0 : Integer.parseInt(rotationString);
+    }
+
+    private void onStop(boolean notifyListener) {
+        mMediaExtractor.release();
+        mMediaExtractor = null;
+
+        if (mVideoTrackDecoder != null) {
+            mVideoTrackDecoder.release();
+            mVideoTrackDecoder = null;
+        }
+
+        if (mAudioTrackDecoder != null) {
+            mAudioTrackDecoder.release();
+            mAudioTrackDecoder = null;
+        }
+
+        if (mOpenGLEnabled) {
+            if (mRenderTarget != null) {
+                getRenderTarget().release();
+            }
+            RenderTarget.focusNone();
+        }
+
+        mVideoTrackIndex = -1;
+        mAudioTrackIndex = -1;
+
+        mEventQueue.clear();
+        mStarted = false;
+        if (notifyListener) {
+            mListener.onDecodingStopped();
+        }
+    }
+
+    private void decode() {
+        int sampleTrackIndex = mMediaExtractor.getSampleTrackIndex();
+        if (sampleTrackIndex >= 0) {
+            if (sampleTrackIndex == mVideoTrackIndex) {
+                mVideoTrackDecoder.feedInput(mMediaExtractor);
+            } else if (sampleTrackIndex == mAudioTrackIndex) {
+                mAudioTrackDecoder.feedInput(mMediaExtractor);
+            }
+        } else if (!mSignaledEndOfInput) {
+            if (mVideoTrackDecoder != null) {
+                mVideoTrackDecoder.signalEndOfInput();
+            }
+            if (mAudioTrackDecoder != null) {
+                mAudioTrackDecoder.signalEndOfInput();
+            }
+            mSignaledEndOfInput = true;
+        }
+
+        if (mVideoTrackDecoder != null) {
+            mVideoTrackDecoder.drainOutputBuffer();
+        }
+        if (mAudioTrackDecoder != null) {
+            mAudioTrackDecoder.drainOutputBuffer();
+        }
+    }
+
+    /**
+     * Fills the argument frame with the video data, using the rotation hint obtained from the
+     * file's metadata, if any.
+     *
+     * @see #grabVideoFrame(FrameImage2D, int)
+     */
+    public void grabVideoFrame(FrameImage2D outputVideoFrame) {
+        grabVideoFrame(outputVideoFrame, mDefaultRotation);
+    }
+
+    /**
+     * Fills the argument frame with the video data, the frame will be returned with the given
+     * rotation applied.
+     *
+     * @param outputVideoFrame the output video frame.
+     * @param videoRotation the rotation angle that is applied to the raw decoded frame.
+     *   Value is one of {ROTATE_NONE, ROTATE_90_RIGHT, ROTATE_180, ROTATE_90_LEFT}.
+     */
+    public void grabVideoFrame(FrameImage2D outputVideoFrame, int videoRotation) {
+        if (mVideoTrackDecoder != null && outputVideoFrame != null) {
+            mVideoTrackDecoder.grabFrame(outputVideoFrame, videoRotation);
+        }
+    }
+
+    /**
+     * Fills the argument frame with the audio data.
+     *
+     * @param outputAudioFrame the output audio frame.
+     */
+    public void grabAudioSamples(FrameValue outputAudioFrame) {
+        if (mAudioTrackDecoder != null) {
+            if (outputAudioFrame != null) {
+                mAudioTrackDecoder.grabSample(outputAudioFrame);
+            } else {
+                mAudioTrackDecoder.clearBuffer();
+            }
+        }
+    }
+
+    /**
+     * Gets the duration, in nanoseconds.
+     */
+    public long getDuration() {
+        if (!mStarted) {
+            throw new IllegalStateException("MediaDecoder has not been started");
+        }
+
+        MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(
+                mVideoTrackIndex != -1 ? mVideoTrackIndex : mAudioTrackIndex);
+        return mediaFormat.getLong(MediaFormat.KEY_DURATION) * 1000;
+    }
+
+    private RenderTarget getRenderTarget() {
+        if (mRenderTarget == null) {
+            mRenderTarget = RenderTarget.newTarget(1, 1);
+        }
+        return mRenderTarget;
+    }
+
+    @Override
+    public void onDecodedOutputAvailable(TrackDecoder decoder) {
+        if (decoder == mVideoTrackDecoder) {
+            mListener.onVideoFrameAvailable();
+        } else if (decoder == mAudioTrackDecoder) {
+            mListener.onAudioSamplesAvailable();
+        }
+    }
+
+    @Override
+    public void onEndOfStream(TrackDecoder decoder) {
+        if (decoder == mAudioTrackDecoder) {
+            mSeenEndOfAudioOutput = true;
+        } else if (decoder == mVideoTrackDecoder) {
+            mSeenEndOfVideoOutput = true;
+        }
+
+        if ((mAudioTrackDecoder == null || mSeenEndOfAudioOutput)
+                && (mVideoTrackDecoder == null || mSeenEndOfVideoOutput)) {
+            stop(false);
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/TrackDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/TrackDecoder.java
new file mode 100644
index 0000000..c81e8b4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/TrackDecoder.java
@@ -0,0 +1,178 @@
+/*
+ * 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+@TargetApi(16)
+abstract class TrackDecoder {
+
+    interface Listener {
+        void onDecodedOutputAvailable(TrackDecoder decoder);
+
+        void onEndOfStream(TrackDecoder decoder);
+    }
+
+    private static final String LOG_TAG = "TrackDecoder";
+
+    private static final long TIMEOUT_US = 50; // Timeout for en-queueing and de-queueing buffers.
+
+    private static final int NO_INPUT_BUFFER = -1;
+
+    private final int mTrackIndex;
+    private final MediaFormat mMediaFormat;
+    private final Listener mListener;
+
+    private MediaCodec mMediaCodec;
+    private MediaFormat mOutputFormat;
+
+    private ByteBuffer[] mCodecInputBuffers;
+    private ByteBuffer[] mCodecOutputBuffers;
+
+    private boolean mShouldEnqueueEndOfStream;
+
+    /**
+     * @return a configured {@link MediaCodec}.
+     */
+    protected abstract MediaCodec initMediaCodec(MediaFormat format);
+
+    /**
+     * Called when decoded output is available. The implementer is responsible for releasing the
+     * assigned buffer.
+     *
+     * @return {@code true} if any further decoding should be attempted at the moment.
+     */
+    protected abstract boolean onDataAvailable(
+            MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info);
+
+    protected TrackDecoder(int trackIndex, MediaFormat mediaFormat, Listener listener) {
+        mTrackIndex = trackIndex;
+
+        if (mediaFormat == null) {
+            throw new NullPointerException("mediaFormat cannot be null");
+        }
+        mMediaFormat = mediaFormat;
+
+        if (listener == null) {
+            throw new NullPointerException("listener cannot be null");
+        }
+        mListener = listener;
+    }
+
+    public void init() {
+        mMediaCodec = initMediaCodec(mMediaFormat);
+        mMediaCodec.start();
+        mCodecInputBuffers = mMediaCodec.getInputBuffers();
+        mCodecOutputBuffers = mMediaCodec.getOutputBuffers();
+    }
+
+    public void signalEndOfInput() {
+        mShouldEnqueueEndOfStream = true;
+        tryEnqueueEndOfStream();
+    }
+
+    public void release() {
+        if (mMediaCodec != null) {
+            mMediaCodec.stop();
+            mMediaCodec.release();
+        }
+    }
+
+    protected MediaCodec getMediaCodec() {
+        return mMediaCodec;
+    }
+
+    protected void notifyListener() {
+        mListener.onDecodedOutputAvailable(this);
+    }
+
+    public boolean feedInput(MediaExtractor mediaExtractor) {
+        long presentationTimeUs = 0;
+
+        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
+        if (inputBufferIndex != NO_INPUT_BUFFER) {
+            ByteBuffer destinationBuffer = mCodecInputBuffers[inputBufferIndex];
+            int sampleSize = mediaExtractor.readSampleData(destinationBuffer, 0);
+            // We don't expect to get a sample without any data, so this should never happen.
+            if (sampleSize < 0) {
+                Log.w(LOG_TAG, "Media extractor had sample but no data.");
+
+                // Signal the end of the track immediately anyway, using the buffer.
+                mMediaCodec.queueInputBuffer(
+                        inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                return false;
+            }
+
+            presentationTimeUs = mediaExtractor.getSampleTime();
+            mMediaCodec.queueInputBuffer(
+                    inputBufferIndex,
+                    0,
+                    sampleSize,
+                    presentationTimeUs,
+                    0);
+
+            return mediaExtractor.advance()
+                    && mediaExtractor.getSampleTrackIndex() == mTrackIndex;
+        }
+        return false;
+    }
+
+    private void tryEnqueueEndOfStream() {
+        int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
+        // We will always eventually have an input buffer, because we keep trying until the last
+        // decoded frame is output.
+        // The EoS does not need to be signaled if the application stops decoding.
+        if (inputBufferIndex != NO_INPUT_BUFFER) {
+            mMediaCodec.queueInputBuffer(
+                    inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+            mShouldEnqueueEndOfStream = false;
+        }
+    }
+
+    public boolean drainOutputBuffer() {
+        BufferInfo outputInfo = new BufferInfo();
+        int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(outputInfo, TIMEOUT_US);
+
+        if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            mListener.onEndOfStream(this);
+            return false;
+        }
+        if (mShouldEnqueueEndOfStream) {
+            tryEnqueueEndOfStream();
+        }
+        if (outputBufferIndex >= 0) {
+            return onDataAvailable(
+                    mMediaCodec, mCodecOutputBuffers, outputBufferIndex, outputInfo);
+        } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+            mCodecOutputBuffers = mMediaCodec.getOutputBuffers();
+            return true;
+        } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+            mOutputFormat = mMediaCodec.getOutputFormat();
+            Log.d(LOG_TAG, "Output format has changed to " + mOutputFormat);
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/VideoTrackDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/VideoTrackDecoder.java
new file mode 100644
index 0000000..06a4305
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/VideoTrackDecoder.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.decoder;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import androidx.media.filterfw.FrameImage2D;
+
+/**
+ * Base class for all {@link TrackDecoder} classes that decode video.
+ */
+@TargetApi(16)
+public abstract class VideoTrackDecoder extends TrackDecoder {
+
+    private static final String LOG_TAG = "VideoTrackDecoder";
+
+    protected final Object mFrameMonitor = new Object();
+    protected volatile boolean mFrameAvailable; // Access guarded by mFrameMonitor.
+
+    protected VideoTrackDecoder(int trackIndex, MediaFormat format, Listener listener) {
+        super(trackIndex, format, listener);
+        if (!DecoderUtil.isVideoFormat(format)) {
+            throw new IllegalArgumentException(
+                    "VideoTrackDecoder can only be used with video formats");
+        }
+    }
+
+    public void grabFrame(FrameImage2D outputVideoFrame, int rotation) {
+        synchronized (mFrameMonitor) {
+            if (!mFrameAvailable) {
+                Log.w(LOG_TAG, "frame is not ready - the caller has to wait for a corresponding " +
+                        "onDecodedFrameAvailable() call");
+                return;
+            }
+
+            copyFrameDataTo(outputVideoFrame, rotation);
+
+            mFrameAvailable = false;
+            mFrameMonitor.notifyAll();
+        }
+    }
+
+
+    /**
+     * Waits for the frame to be picked up by the MFF thread, i.e. blocks until the
+     * {@link #grabFrame(FrameImage2D, int)}) method is called.
+     */
+    public boolean waitForFrameGrab() {
+        synchronized (mFrameMonitor) {
+            try {
+                while (mFrameAvailable) {
+                    mFrameMonitor.wait();
+                }
+                return true;
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+
+    protected final void markFrameAvailable() {
+        synchronized (mFrameMonitor) {
+            mFrameAvailable = true;
+            mFrameMonitor.notifyAll();
+        }
+    }
+
+    /**
+     * @return if the frame dimension needs to be swapped,
+     *   i.e. (width,height) becomes (height, width)
+     */
+    protected static boolean needSwapDimension(int rotation) {
+        switch(rotation) {
+            case MediaDecoder.ROTATE_90_RIGHT:
+            case MediaDecoder.ROTATE_90_LEFT:
+                return true;
+            case MediaDecoder.ROTATE_NONE:
+            case MediaDecoder.ROTATE_180:
+                return false;
+            default:
+                throw new IllegalArgumentException("Unsupported rotation angle.");
+        }
+    }
+
+    /**
+     * Subclasses must implement this to copy the video frame data to an MFF frame.
+     *
+     * @param outputVideoFrame The destination frame
+     * @param rotation The desired rotation of the frame
+     */
+    protected abstract void copyFrameDataTo(FrameImage2D outputVideoFrame, int rotation);
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/geometry/Quad.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/geometry/Quad.java
new file mode 100644
index 0000000..4035f7f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/geometry/Quad.java
@@ -0,0 +1,346 @@
+/*
+ * 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 androidx.media.filterfw.geometry;
+
+import android.annotation.SuppressLint;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * The Quad class specifies a (possibly affine transformed) rectangle.
+ *
+ * A Quad instance holds 4 points that define its shape. The points may represent any rectangle that
+ * has been transformed by an affine transformation. This means that Quads can represent translated,
+ * scaled, rotated and sheared/skewed rectangles. As such, Quads are restricted to the set of
+ * parallelograms.
+ *
+ * Each point in the Quad represents a specific corner of the Quad. These are top-left, top-right,
+ * bottom-left, and bottom-right. These labels allow mapping a transformed Quad back to an up-right
+ * Quad, with the point-to-point mapping well-defined. They do not necessarily indicate that e.g.
+ * the top-left corner is actually at the top-left of coordinate space.
+ */
+@SuppressLint("FloatMath")
+public class Quad {
+
+    private final PointF mTopLeft;
+    private final PointF mTopRight;
+    private final PointF mBottomLeft;
+    private final PointF mBottomRight;
+
+    /**
+     * Returns the unit Quad.
+     * The unit Quad has its top-left point at (0, 0) and bottom-right point at (1, 1).
+     * @return the unit Quad.
+     */
+    public static Quad unitQuad() {
+        return new Quad(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f);
+    }
+
+    /**
+     * Return a Quad from the specified rectangle.
+     *
+     * @param rect a RectF instance.
+     * @return Quad that represents the passed rectangle.
+     */
+    public static Quad fromRect(RectF rect) {
+        return new Quad(new PointF(rect.left, rect.top),
+                        new PointF(rect.right, rect.top),
+                        new PointF(rect.left, rect.bottom),
+                        new PointF(rect.right, rect.bottom));
+    }
+
+    /**
+     * Return a Quad from the specified rectangle coordinates.
+     *
+     * @param x the top left x coordinate
+     * @param y the top left y coordinate
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     * @return Quad that represents the passed rectangle.
+     */
+    public static Quad fromRect(float x, float y, float width, float height) {
+        return new Quad(new PointF(x, y),
+                        new PointF(x + width, y),
+                        new PointF(x, y + height),
+                        new PointF(x + width, y + height));
+    }
+
+    /**
+     * Return a Quad that spans the specified points and height.
+     *
+     * The returned Quad has the specified top-left and top-right points, and the specified height
+     * while maintaining 90 degree angles on all 4 corners.
+     *
+     * @param topLeft the top-left of the quad
+     * @param topRight the top-right of the quad
+     * @param height the height of the quad
+     * @return Quad that spans the specified points and height.
+     */
+    public static Quad fromLineAndHeight(PointF topLeft, PointF topRight, float height) {
+        PointF dp = new PointF(topRight.x - topLeft.x, topRight.y - topLeft.y);
+        float len = dp.length();
+        PointF np = new PointF(height * (dp.y / len), height * (dp.x / len));
+        PointF p2 = new PointF(topLeft.x - np.x, topLeft.y + np.y);
+        PointF p3 = new PointF(topRight.x - np.x, topRight.y + np.y);
+        return new Quad(topLeft, topRight, p2, p3);
+    }
+
+    /**
+     * Return a Quad that represents the specified rotated rectangle.
+     *
+     * The Quad is rotated counter-clockwise around its centroid.
+     *
+     * @param rect the source rectangle
+     * @param angle the angle to rotate the source rectangle in radians
+     * @return the Quad representing the source rectangle rotated by the given angle.
+     */
+    public static Quad fromRotatedRect(RectF rect, float angle) {
+        return Quad.fromRect(rect).rotated(angle);
+    }
+
+    /**
+     * Return a Quad that represents the specified transformed rectangle.
+     *
+     * The transform is applied by multiplying each point (x, y, 1) by the matrix.
+     *
+     * @param rect the source rectangle
+     * @param matrix the transformation matrix
+     * @return the Quad representing the source rectangle transformed by the matrix
+     */
+    public static Quad fromTransformedRect(RectF rect, Matrix matrix) {
+        return Quad.fromRect(rect).transformed(matrix);
+    }
+
+    /**
+     * Returns the transformation matrix to transform the source Quad to the target Quad.
+     *
+     * @param source the source quad
+     * @param target the target quad
+     * @return the transformation matrix to map source to target.
+     */
+    public static Matrix getTransform(Quad source, Quad target) {
+        // We only use the first 3 points as they sufficiently specify the transform
+        Matrix transform = new Matrix();
+        transform.setPolyToPoly(source.asCoords(), 0, target.asCoords(), 0, 3);
+        return transform;
+    }
+
+    /**
+     * The top-left point of the Quad.
+     * @return top-left point of the Quad.
+     */
+    public PointF topLeft() {
+        return mTopLeft;
+    }
+
+    /**
+     * The top-right point of the Quad.
+     * @return top-right point of the Quad.
+     */
+    public PointF topRight() {
+        return mTopRight;
+    }
+
+    /**
+     * The bottom-left point of the Quad.
+     * @return bottom-left point of the Quad.
+     */
+    public PointF bottomLeft() {
+        return mBottomLeft;
+    }
+
+    /**
+     * The bottom-right point of the Quad.
+     * @return bottom-right point of the Quad.
+     */
+    public PointF bottomRight() {
+        return mBottomRight;
+    }
+
+    /**
+     * Rotate the quad by the given angle.
+     *
+     * The Quad is rotated counter-clockwise around its centroid.
+     *
+     * @param angle the angle to rotate in radians
+     * @return the rotated Quad
+     */
+    public Quad rotated(float angle) {
+        PointF center = center();
+        float cosa = (float) Math.cos(angle);
+        float sina = (float) Math.sin(angle);
+
+        PointF topLeft = rotatePoint(topLeft(), center, cosa, sina);
+        PointF topRight = rotatePoint(topRight(), center, cosa, sina);
+        PointF bottomLeft = rotatePoint(bottomLeft(), center, cosa, sina);
+        PointF bottomRight = rotatePoint(bottomRight(), center, cosa, sina);
+
+        return new Quad(topLeft, topRight, bottomLeft, bottomRight);
+    }
+
+    /**
+     * Transform the quad with the given transformation matrix.
+     *
+     * The transform is applied by multiplying each point (x, y, 1) by the matrix.
+     *
+     * @param matrix the transformation matrix
+     * @return the transformed Quad
+     */
+    public Quad transformed(Matrix matrix) {
+        float[] points = asCoords();
+        matrix.mapPoints(points);
+        return new Quad(points);
+    }
+
+    /**
+     * Returns the centroid of the Quad.
+     *
+     * The centroid of the Quad is where the two inner diagonals connecting the opposite corners
+     * meet.
+     *
+     * @return the centroid of the Quad.
+     */
+    public PointF center() {
+        // As the diagonals bisect each other, we can simply return the center of one of the
+        // diagonals.
+        return new PointF((mTopLeft.x + mBottomRight.x) / 2f,
+                          (mTopLeft.y + mBottomRight.y) / 2f);
+    }
+
+    /**
+     * Returns the quad as a float-array of coordinates.
+     * The order of coordinates is top-left, top-right, bottom-left, bottom-right. This is the
+     * default order of coordinates used in ImageShaders, so this method can be used to bind
+     * an attribute to the Quad.
+     */
+    public float[] asCoords() {
+        return new float[] { mTopLeft.x, mTopLeft.y,
+                             mTopRight.x, mTopRight.y,
+                             mBottomLeft.x, mBottomLeft.y,
+                             mBottomRight.x, mBottomRight.y };
+    }
+
+    /**
+     * Grow the Quad outwards by the specified factor.
+     *
+     * This method moves the corner points of the Quad outward along the diagonals that connect
+     * them to the centroid. A factor of 1.0 moves the quad outwards by the distance of the corners
+     * to the centroid.
+     *
+     * @param factor the growth factor
+     * @return the Quad grown by the specified amount
+     */
+    public Quad grow(float factor) {
+        PointF pc = center();
+        return new Quad(factor * (mTopLeft.x - pc.x) + pc.x,
+                        factor * (mTopLeft.y - pc.y) + pc.y,
+                        factor * (mTopRight.x - pc.x) + pc.x,
+                        factor * (mTopRight.y - pc.y) + pc.y,
+                        factor * (mBottomLeft.x - pc.x) + pc.x,
+                        factor * (mBottomLeft.y - pc.y) + pc.y,
+                        factor * (mBottomRight.x - pc.x) + pc.x,
+                        factor * (mBottomRight.y - pc.y) + pc.y);
+    }
+
+    /**
+     * Scale the Quad by the specified factor.
+     *
+     * @param factor the scaling factor
+     * @return the Quad instance scaled by the specified factor.
+     */
+    public Quad scale(float factor) {
+        return new Quad(mTopLeft.x * factor, mTopLeft.y * factor,
+                        mTopRight.x * factor, mTopRight.y * factor,
+                        mBottomLeft.x * factor, mBottomLeft.y * factor,
+                        mBottomRight.x * factor, mBottomRight.y * factor);
+    }
+
+    /**
+     * Scale the Quad by the specified factors in the x and y factors.
+     *
+     * @param sx the x scaling factor
+     * @param sy the y scaling factor
+     * @return the Quad instance scaled by the specified factors.
+     */
+    public Quad scale2(float sx, float sy) {
+        return new Quad(mTopLeft.x * sx, mTopLeft.y * sy,
+                        mTopRight.x * sx, mTopRight.y * sy,
+                        mBottomLeft.x * sx, mBottomLeft.y * sy,
+                        mBottomRight.x * sx, mBottomRight.y * sy);
+    }
+
+    /**
+     * Returns the Quad's left-to-right edge.
+     *
+     * Returns a vector that goes from the Quad's top-left to top-right (or bottom-left to
+     * bottom-right).
+     *
+     * @return the edge vector as a PointF.
+     */
+    public PointF xEdge() {
+        return new PointF(mTopRight.x - mTopLeft.x, mTopRight.y - mTopLeft.y);
+    }
+
+    /**
+     * Returns the Quad's top-to-bottom edge.
+     *
+     * Returns a vector that goes from the Quad's top-left to bottom-left (or top-right to
+     * bottom-right).
+     *
+     * @return the edge vector as a PointF.
+     */
+    public PointF yEdge() {
+        return new PointF(mBottomLeft.x - mTopLeft.x, mBottomLeft.y - mTopLeft.y);
+    }
+
+    @Override
+    public String toString() {
+        return "Quad(" + mTopLeft.x + ", " + mTopLeft.y + ", "
+                       + mTopRight.x + ", " + mTopRight.y + ", "
+                       + mBottomLeft.x + ", " + mBottomLeft.y + ", "
+                       + mBottomRight.x + ", " + mBottomRight.y + ")";
+    }
+
+    private Quad(PointF topLeft, PointF topRight, PointF bottomLeft, PointF bottomRight) {
+        mTopLeft = topLeft;
+        mTopRight = topRight;
+        mBottomLeft = bottomLeft;
+        mBottomRight = bottomRight;
+    }
+
+    private Quad(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) {
+        mTopLeft = new PointF(x0, y0);
+        mTopRight = new PointF(x1, y1);
+        mBottomLeft = new PointF(x2, y2);
+        mBottomRight = new PointF(x3, y3);
+    }
+
+    private Quad(float[] points) {
+        mTopLeft = new PointF(points[0], points[1]);
+        mTopRight = new PointF(points[2], points[3]);
+        mBottomLeft = new PointF(points[4], points[5]);
+        mBottomRight = new PointF(points[6], points[7]);
+    }
+
+    private static PointF rotatePoint(PointF p, PointF c, float cosa, float sina) {
+        float x = (p.x - c.x) * cosa - (p.y - c.y) * sina + c.x;
+        float y = (p.x - c.x) * sina + (p.y - c.y) * cosa + c.y;
+        return new PointF(x,y);
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AverageFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AverageFilter.java
new file mode 100644
index 0000000..d873e0a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AverageFilter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Takes sharpness scores in RT and averages them over time
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public class AverageFilter extends Filter {
+
+    private static final String TAG = "AverageFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int NUM_FRAMES = 5;
+    private int counter = 0;
+    private float[] temp = new float[NUM_FRAMES];
+
+    /**
+     * @param context
+     * @param name
+     */
+    public AverageFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType floatT = FrameType.single(float.class);
+        return new Signature()
+        .addInputPort("sharpness", Signature.PORT_REQUIRED, floatT)
+        .addOutputPort("avg", Signature.PORT_REQUIRED, floatT)
+        .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameValue inFrameValue = getConnectedInputPort("sharpness").pullFrame().asFrameValue();
+        if (counter < NUM_FRAMES && counter >= 0) {
+            temp[counter] = ((Float)inFrameValue.getValue()).floatValue();
+        }
+
+        counter = (counter + 1) % NUM_FRAMES;
+
+        float output = (temp[0] + temp[1] + temp[2] + temp[3] + temp[4]) / NUM_FRAMES;
+        if (mLogVerbose) Log.v(TAG, "Avg= " + output + "temp1= " + temp[0] + "temp2= " +
+                temp[1] + "temp3= " + temp[2] + "temp4=" + temp[3] + "temp5=" + temp[4]);
+
+        OutputPort outPort = getConnectedOutputPort("avg");
+        FrameValue outFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        outFrame.setValue(output);
+        outPort.pushFrame(outFrame);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilter.java
new file mode 100644
index 0000000..88cd44a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+
+public class AvgBrightnessFilter extends Filter {
+
+    private static final String TAG = "AvgBrightnessFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public AvgBrightnessFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU);
+        FrameType floatT = FrameType.single(float.class);
+        return new Signature().addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+                .addOutputPort("brightnessRating", Signature.PORT_OPTIONAL, floatT)
+                .disallowOtherPorts();
+
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+
+        float brightness;
+        ByteBuffer inputBuffer  = inputImage.lockBytes(Frame.MODE_READ);
+
+        brightness = brightnessOperator(inputImage.getWidth(),inputImage.getHeight(), inputBuffer);
+
+        inputImage.unlock();
+
+        if (mLogVerbose) Log.v(TAG, "contrastRatio: " + brightness);
+
+        OutputPort brightnessPort = getConnectedOutputPort("brightnessRating");
+        FrameValue brightnessOutFrame = brightnessPort.fetchAvailableFrame(null).asFrameValue();
+        brightnessOutFrame.setValue(brightness);
+        brightnessPort.pushFrame(brightnessOutFrame);
+    }
+
+    private static native float brightnessOperator(int width, int height, ByteBuffer imageBuffer);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CSVWriterFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CSVWriterFilter.java
new file mode 100644
index 0000000..ca16c27
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CSVWriterFilter.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+
+public class CSVWriterFilter extends Filter {
+
+    private static final String TAG = "CSVWriterFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    private boolean mFirstTime = true;
+    private final static int NUM_FRAMES = 3;
+    private final String mFileName = "/CSVFile.csv";
+
+    public CSVWriterFilter(MffContext context, String name) {
+
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType floatT = FrameType.single(float.class);
+        FrameType stringT = FrameType.single(String.class);
+        FrameType floatArrayT = FrameType.array(float.class);
+
+        return new Signature()
+                .addInputPort("sharpness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("overExposure", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("underExposure", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("colorfulness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("contrastRating", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("brightness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("motionValues", Signature.PORT_REQUIRED, floatArrayT)
+                .addInputPort("imageFileName", Signature.PORT_REQUIRED, stringT)
+                .addInputPort("csvFilePath", Signature.PORT_REQUIRED, stringT)
+                .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+
+
+
+        Log.v(TAG,"in csv writer on process");
+        FrameValue sharpnessValue =
+                getConnectedInputPort("sharpness").pullFrame().asFrameValue();
+        float sharpness = ((Float)sharpnessValue.getValue()).floatValue();
+
+        FrameValue overExposureValue =
+                getConnectedInputPort("overExposure").pullFrame().asFrameValue();
+        float overExposure = ((Float)overExposureValue.getValue()).floatValue();
+
+        FrameValue underExposureValue =
+                getConnectedInputPort("underExposure").pullFrame().asFrameValue();
+        float underExposure = ((Float)underExposureValue.getValue()).floatValue();
+
+        FrameValue colorfulnessValue =
+                getConnectedInputPort("colorfulness").pullFrame().asFrameValue();
+        float colorfulness = ((Float)colorfulnessValue.getValue()).floatValue();
+
+        FrameValue contrastValue =
+                getConnectedInputPort("contrastRating").pullFrame().asFrameValue();
+        float contrast = ((Float)contrastValue.getValue()).floatValue();
+
+        FrameValue brightnessValue =
+                getConnectedInputPort("brightness").pullFrame().asFrameValue();
+        float brightness = ((Float)brightnessValue.getValue()).floatValue();
+
+        FrameValue motionValuesFrameValue =
+                getConnectedInputPort("motionValues").pullFrame().asFrameValue();
+        float[] motionValues = (float[]) motionValuesFrameValue.getValue();
+        float vectorAccel = (float) Math.sqrt(Math.pow(motionValues[0], 2) +
+                Math.pow(motionValues[1], 2) + Math.pow(motionValues[2], 2));
+
+        FrameValue imageFileNameFrameValue =
+                getConnectedInputPort("imageFileName").pullFrame().asFrameValue();
+        String imageFileName = ((String)imageFileNameFrameValue.getValue());
+
+        FrameValue csvFilePathFrameValue =
+                getConnectedInputPort("csvFilePath").pullFrame().asFrameValue();
+        String csvFilePath = ((String)csvFilePathFrameValue.getValue());
+
+
+        if(mFirstTime) {
+            try {
+                FileWriter fileWriter = new FileWriter(csvFilePath + "/CSVFile.csv");
+                BufferedWriter csvWriter = new BufferedWriter(fileWriter);
+
+                csvWriter.write("FileName,Sharpness,OverExposure,UnderExposure,Colorfulness," +
+                            "ContrastRating,Brightness,Motion");
+                csvWriter.newLine();
+                csvWriter.close();
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+            mFirstTime = false;
+        }
+
+        try {
+            Log.v(TAG,"about to write to file");
+            FileWriter fileWriter = new FileWriter(csvFilePath + mFileName, true);
+            BufferedWriter csvWriter = new BufferedWriter(fileWriter);
+
+            csvWriter.write(imageFileName + "," + sharpness + "," + overExposure + "," +
+                    underExposure + "," + colorfulness + "," + contrast + "," + brightness +
+                    "," + vectorAccel);
+            Log.v(TAG, "" + imageFileName + "," + sharpness + "," + overExposure + "," +
+                    underExposure + "," + colorfulness + "," + contrast + "," + brightness +
+                    "," + vectorAccel);
+            csvWriter.newLine();
+            csvWriter.close();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java
new file mode 100644
index 0000000..fa0f995
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/Camera2Source.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.os.Handler;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.renderscript.Type;
+import android.util.Log;
+import android.view.Surface;
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Camera2Source extends Filter implements Allocation.OnBufferAvailableListener {
+
+    private boolean mNewFrameAvailable = false;
+    private FrameType mOutputType;
+    private static final String TAG = "Camera2Source";
+    private CameraManager mCameraManager;
+    private CameraDevice mCamera;
+    private RenderScript mRS;
+    private Surface mSurface;
+    private CameraCharacteristics mProperties;
+    private CameraTestThread mLooperThread;
+    private int mHeight = 480;
+    private int mWidth = 640;
+    private Allocation mAllocationIn;
+    private ScriptIntrinsicYuvToRGB rgbConverter;
+    private Allocation mAllocationOut;
+    private Bitmap mBitmap;
+
+    class MyCameraListener extends CameraManager.AvailabilityListener {
+
+        @Override
+        public void onCameraAvailable(String cameraId) {
+            // TODO Auto-generated method stub
+            Log.v(TAG, "camera available to open");
+        }
+
+        @Override
+        public void onCameraUnavailable(String cameraId) {
+            // TODO Auto-generated method stub
+            Log.v(TAG, "camera unavailable to open");
+        }
+
+    }
+
+    class MyCaptureListener extends CameraDevice.CaptureListener {
+
+        @Override
+        public void onCaptureCompleted(CameraDevice camera, CaptureRequest request,
+                CaptureResult result) {
+            // TODO Auto-generated method stub
+            Log.v(TAG, "in onCaptureComplete");
+
+        }
+
+        @Override
+        public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
+                CaptureFailure failure) {
+            // TODO Auto-generated method stub
+            Log.v(TAG, "onCaptureFailed is being called");
+        }
+
+    }
+
+    public Camera2Source(MffContext context, String name) {
+        super(context, name);
+        mOutputType = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+
+        Context ctx = context.getApplicationContext();
+        mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
+
+        mRS = RenderScript.create(context.getApplicationContext());
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+                .addOutputPort("timestamp", Signature.PORT_OPTIONAL, FrameType.single(long.class))
+                .addOutputPort("video", Signature.PORT_REQUIRED, mOutputType)
+                .addOutputPort("orientation", Signature.PORT_REQUIRED,
+                        FrameType.single(float.class))
+                .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onClose() {
+        Log.v(TAG, "onClose being called");
+        try {
+            mCamera.close();
+            mSurface.release();
+            mLooperThread.close();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onOpen() {
+        mLooperThread = new CameraTestThread();
+        Handler mHandler;
+        try {
+            mHandler = mLooperThread.start();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+
+        try {
+            String backCameraId = "0";
+            BlockingCameraManager blkManager = new BlockingCameraManager(mCameraManager);
+            mCamera = blkManager.openCamera(backCameraId, /*listener*/null, mHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        } catch (BlockingOpenException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+
+        Element ele = Element.createPixel(mRS, Element.DataType.UNSIGNED_8,
+                Element.DataKind.PIXEL_YUV);
+
+        rgbConverter = ScriptIntrinsicYuvToRGB.create(mRS,ele);
+        Type.Builder yuvBuilder = new Type.Builder(mRS,ele);
+
+        yuvBuilder.setYuvFormat(ImageFormat.YUV_420_888);
+        yuvBuilder.setX(mWidth);
+        yuvBuilder.setY(mHeight);
+        mAllocationIn = Allocation.createTyped(mRS, yuvBuilder.create(),
+                Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT);
+        mSurface = mAllocationIn.getSurface();
+        mAllocationIn.setOnBufferAvailableListener(this);
+        rgbConverter.setInput(mAllocationIn);
+
+        mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
+        mAllocationOut = Allocation.createFromBitmap(mRS, mBitmap);
+
+
+        Log.v(TAG, "mcamera: " + mCamera);
+
+        List<Surface> surfaces = new ArrayList<Surface>();
+        surfaces.add(mSurface);
+        CaptureRequest.Builder mCaptureRequest = null;
+        try {
+            mCamera.configureOutputs(surfaces);
+            mCaptureRequest = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            mCaptureRequest.addTarget(mSurface);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+
+        try {
+            mCamera.setRepeatingRequest(mCaptureRequest.build(), new MyCaptureListener(),
+                    mHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+        mProperties = null;
+        try {
+            mProperties = mCameraManager.getCameraCharacteristics(mCamera.getId());
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    protected void onProcess() {
+        Log.v(TAG, "on Process");
+        if (nextFrame()) {
+            OutputPort outPort = getConnectedOutputPort("video");
+
+            // Create a 2D frame that will hold the output
+            int[] dims = new int[] {
+                    mWidth, mHeight
+            };
+            FrameImage2D outputFrame = Frame.create(mOutputType, dims).asFrameImage2D();
+            rgbConverter.forEach(mAllocationOut);
+            mAllocationOut.copyTo(mBitmap);
+            outputFrame.setBitmap(mBitmap);
+            outPort.pushFrame(outputFrame);
+            outputFrame.release();
+
+            OutputPort orientationPort = getConnectedOutputPort("orientation");
+            FrameValue orientationFrame = orientationPort.fetchAvailableFrame(null).asFrameValue();
+
+            // FIXME: Hardcoded value because ORIENTATION returns null, Qualcomm
+            // bug
+            Integer orientation = mProperties.get(CameraCharacteristics.SENSOR_ORIENTATION);
+            float temp;
+            if (orientation != null) {
+                temp = orientation.floatValue();
+            } else {
+                temp = 90.0f;
+            }
+            orientationFrame.setValue(temp);
+            orientationPort.pushFrame(orientationFrame);
+        }
+    }
+
+    private synchronized boolean nextFrame() {
+        boolean frameAvailable = mNewFrameAvailable;
+        if (frameAvailable) {
+            mNewFrameAvailable = false;
+        } else {
+            enterSleepState();
+        }
+        return frameAvailable;
+    }
+
+    public void onBufferAvailable(Allocation a) {
+        Log.v(TAG, "on Buffer Available");
+        a.ioReceive();
+        synchronized (this) {
+            mNewFrameAvailable = true;
+        }
+        wakeUp();
+    }
+
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CameraTestThread.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CameraTestThread.java
new file mode 100644
index 0000000..8a0fced
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/CameraTestThread.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Camera test thread wrapper for handling camera callbacks
+ */
+public class CameraTestThread implements AutoCloseable {
+    private static final String TAG = "CameraTestThread";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    // Timeout for initializing looper and opening camera in Milliseconds.
+    private static final long WAIT_FOR_COMMAND_TO_COMPLETE = 5000;
+    private Looper mLooper = null;
+    private Handler mHandler = null;
+
+    /**
+     * Create and start a looper thread, return the Handler
+     */
+    public synchronized Handler start() throws Exception {
+        final ConditionVariable startDone = new ConditionVariable();
+        if (mLooper != null || mHandler !=null) {
+            Log.w(TAG, "Looper thread already started");
+            return mHandler;
+        }
+
+        new Thread() {
+            @Override
+            public void run() {
+                if (VERBOSE) Log.v(TAG, "start loopRun");
+                Looper.prepare();
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+                mHandler = new Handler();
+                startDone.open();
+                Looper.loop();
+                if (VERBOSE) Log.v(TAG, "createLooperThread: finished");
+            }
+        }.start();
+
+        if (VERBOSE) Log.v(TAG, "start waiting for looper");
+        if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
+            throw new TimeoutException("createLooperThread: start timeout");
+        }
+        return mHandler;
+    }
+
+    /**
+     * Terminate the looper thread
+     */
+    public synchronized void close() throws Exception {
+        if (mLooper == null || mHandler == null) {
+            Log.w(TAG, "Looper thread doesn't start yet");
+            return;
+        }
+
+        if (VERBOSE) Log.v(TAG, "Terminate looper thread");
+        mLooper.quit();
+        mLooper.getThread().join();
+        mLooper = null;
+        mHandler = null;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilter.java
new file mode 100644
index 0000000..d918437
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+
+public class ContrastRatioFilter extends Filter {
+
+    private static final String TAG = "ContrastRatioFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public ContrastRatioFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU);
+        FrameType floatT = FrameType.single(float.class);
+        return new Signature().addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+                .addOutputPort("contrastRatingToGoodness", Signature.PORT_REQUIRED, floatT)
+                .disallowOtherPorts();
+
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+
+        float contrastRatio;
+        ByteBuffer inputBuffer  = inputImage.lockBytes(Frame.MODE_READ);
+
+        contrastRatio = contrastOperator(inputImage.getWidth(), inputImage.getHeight(),
+                    inputBuffer);
+
+        inputImage.unlock();
+
+        if (mLogVerbose) Log.v(TAG, "contrastRatio: " + contrastRatio);
+
+        OutputPort contrastToGoodnessPort = getConnectedOutputPort("contrastRatingToGoodness");
+        FrameValue contrastOutFrame2 =
+                contrastToGoodnessPort.fetchAvailableFrame(null).asFrameValue();
+        contrastOutFrame2.setValue(contrastRatio);
+        contrastToGoodnessPort.pushFrame(contrastOutFrame2);
+
+
+    }
+
+    private static native float contrastOperator(int width, int height, ByteBuffer imageBuffer);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ExposureFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ExposureFilter.java
new file mode 100644
index 0000000..6128718
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ExposureFilter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+
+public class ExposureFilter extends Filter {
+
+    private FrameType mImageType;
+    private static final String TAG = "ExposureFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    private static int OVER_EXPOSURE_TOLERANCE = 5;
+
+    public ExposureFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType floatT = FrameType.single(float.class);
+        return new Signature().addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+                .addOutputPort("overExposedNum", Signature.PORT_OPTIONAL, floatT)
+                .addOutputPort("overExposureRating", Signature.PORT_REQUIRED, floatT)
+                .addOutputPort("underExposedNum", Signature.PORT_OPTIONAL, floatT)
+                .addOutputPort("underExposureRating", Signature.PORT_REQUIRED, floatT)
+                .disallowOtherPorts();
+
+    }
+
+    @Override
+    protected void onProcess() {
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+
+        float overExposedPixels, underExposedPixels;
+        ByteBuffer inputBuffer = inputImage.lockBytes(Frame.MODE_READ);
+
+        overExposedPixels = overExposureOperator(inputImage.getWidth(),
+                                                 inputImage.getHeight(),
+                                                 inputBuffer);
+        underExposedPixels = underExposureOperator(inputImage.getWidth(),
+                                                   inputImage.getHeight(),
+                                                   inputBuffer);
+        inputImage.unlock();
+
+
+        if (mLogVerbose) Log.v(TAG, "underExposedPixelCount: " + underExposedPixels);
+
+        OutputPort underPort = getConnectedOutputPort("underExposedNum");
+        if (underPort != null) {
+            FrameValue underOutFrame = underPort.fetchAvailableFrame(null).asFrameValue();
+            underOutFrame.setValue(underExposedPixels*inputImage.getWidth()*inputImage.getHeight());
+            underPort.pushFrame(underOutFrame);
+        }
+
+
+        OutputPort underPort2 = getConnectedOutputPort("underExposureRating");
+        FrameValue underOutFrame2 = underPort2.fetchAvailableFrame(null).asFrameValue();
+        underOutFrame2.setValue(underExposedPixels);
+        underPort2.pushFrame(underOutFrame2);
+
+        if (mLogVerbose) Log.v(TAG, "overExposedPixelCount: " + overExposedPixels);
+
+        OutputPort overPort = getConnectedOutputPort("overExposedNum");
+        if (overPort != null) {
+            FrameValue overOutFrame = overPort.fetchAvailableFrame(null).asFrameValue();
+            overOutFrame.setValue(overExposedPixels*inputImage.getWidth()*inputImage.getHeight());
+            overPort.pushFrame(overOutFrame);
+        }
+
+
+        OutputPort overPort2 = getConnectedOutputPort("overExposureRating");
+        FrameValue overOutFrame2 = overPort2.fetchAvailableFrame(null).asFrameValue();
+        overOutFrame2.setValue(overExposedPixels);
+        overPort2.pushFrame(overOutFrame2);
+
+    }
+
+    private static native float overExposureOperator(int width, int height,
+            ByteBuffer imageBuffer);
+    private static native float underExposureOperator(int width, int height,
+            ByteBuffer imageBuffer);
+
+    static {
+        System.loadLibrary("smartcamera_jni");
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilter.java
new file mode 100644
index 0000000..c4a39e8
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilter.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Takes in an array, returns the size of the array
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.Camera.Face;
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.Frame;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValues;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.nio.ByteBuffer;
+
+public class FaceSquareFilter extends Filter {
+
+    private static final String TAG = "FaceSquareFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static int FACE_X_RANGE = 2000;
+    private static int WIDTH_OFFSET = 1000;
+    private static int HEIGHT_OFFSET = 1000;
+
+    public FaceSquareFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageType = FrameType.buffer2D(FrameType.ELEMENT_RGBA8888);
+        FrameType facesType = FrameType.array(Camera.Face.class);
+        return new Signature()
+                .addInputPort("image", Signature.PORT_REQUIRED, imageType)
+                .addInputPort("faces", Signature.PORT_REQUIRED, facesType)
+                .addOutputPort("image", Signature.PORT_REQUIRED, imageType)
+                .disallowOtherPorts();
+    }
+
+    /**
+     * @see androidx.media.filterfw.Filter#onProcess()
+     */
+    @Override
+    protected void onProcess() {
+        // Get inputs
+        FrameImage2D imageFrame = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+        FrameValues facesFrame = getConnectedInputPort("faces").pullFrame().asFrameValues();
+        Face[] faces = (Face[]) facesFrame.getValues();
+        int[] dims = imageFrame.getDimensions();
+        ByteBuffer buffer = imageFrame.lockBytes(Frame.MODE_WRITE);
+        byte[] pixels = buffer.array();
+
+        // For every face in faces, draw a white rect around the
+        // face following the rect member of the Face
+        drawBoxes(pixels, faces, dims);
+
+        imageFrame.unlock();
+
+        OutputPort outPort = getConnectedOutputPort("image");
+        outPort.pushFrame(imageFrame);
+    }
+
+    public void drawBoxes(byte[] pixels, Face[] faces, int[] dims) {
+        for(int i = 0; i < faces.length; i++) {
+            Rect tempRect = faces[i].rect;
+            int top = (tempRect.top+HEIGHT_OFFSET)*dims[1]/FACE_X_RANGE;
+            int bottom = (tempRect.bottom+HEIGHT_OFFSET)*dims[1]/FACE_X_RANGE;
+            int left = (tempRect.left+WIDTH_OFFSET)*dims[0]/FACE_X_RANGE;
+            int right = (tempRect.right+WIDTH_OFFSET)*dims[0]/FACE_X_RANGE;
+
+            if (top < 0) {
+                top = 0;
+            } else if (top > dims[1]) {
+                top = dims[1];
+            }
+            if (left < 0) {
+                left = 0;
+            } else if (left > dims[0]) {
+                left = dims[0];
+            }
+            if (bottom > dims[1]) {
+                bottom = dims[1];
+            } else if (bottom < 0) {
+                bottom = 0;
+            }
+            if (right > dims[0]) {
+                right = dims[0];
+            } else if (right < 0) {
+                right = 0;
+            }
+
+            for (int j = 0; j < (bottom - top); j++) {
+                // Left edge
+                if (left > 0 && top > 0) {
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + left) +
+                           ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + left) +
+                           ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + left) +
+                           ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                }
+
+                // Right edge
+                if (right > 0 && top > 0) {
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + right) +
+                           ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + right) +
+                           ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * (top + j) + right) +
+                           ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                }
+
+            }
+            for (int k = 0; k < (right - left); k++) {
+                // Top edge
+                if (top < dims[1]) {
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * top + left + k) +
+                           ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * top + left + k) +
+                           ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * top + left + k) +
+                           ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+
+                }
+                // Bottom edge
+                if (bottom < dims[1]) {
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * bottom + left + k) +
+                           ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * bottom + left + k) +
+                           ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                    pixels[ImageConstants.PIX_CHANNELS * (dims[0] * bottom + left + k) +
+                           ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                }
+
+
+            }
+
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilter.java
new file mode 100644
index 0000000..72452b3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Takes in an array, returns the size of the array
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.lang.reflect.Array;
+
+public class FloatArrayToSizeFilter extends Filter {
+
+    private static final String TAG = "FloatArrayToSizeFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    /**
+     * @param context
+     * @param name
+     */
+    public FloatArrayToSizeFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType intT = FrameType.single(int.class);
+        FrameType floatType = FrameType.array(float.class);
+
+        return new Signature()
+                .addInputPort("array", Signature.PORT_REQUIRED, floatType)
+                .addOutputPort("size", Signature.PORT_REQUIRED, intT)
+                .disallowOtherPorts();
+    }
+
+    /**
+     * @see androidx.media.filterfw.Filter#onProcess()
+     */
+    @Override
+    protected void onProcess() {
+        FrameValue arrayFrame = getConnectedInputPort("array").pullFrame().asFrameValues();
+        Object array = arrayFrame.getValue();
+        int size = Array.getLength(array);
+
+        OutputPort outPort = getConnectedOutputPort("size");
+        FrameValue sizeFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        sizeFrame.setValue(size);
+        outPort.pushFrame(sizeFrame);
+
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilter.java
new file mode 100644
index 0000000..4606cfb
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Takes in an array, returns the size of the array
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+public class FloatArrayToStrFilter extends Filter {
+
+    private static final String TAG = "FloatArrayToStrFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /**
+     * @param context
+     * @param name
+     */
+    public FloatArrayToStrFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType floatType = FrameType.array(float.class);
+
+        return new Signature()
+                .addInputPort("array", Signature.PORT_REQUIRED, floatType)
+                .addOutputPort("string", Signature.PORT_REQUIRED, FrameType.single(String.class))
+                .disallowOtherPorts();
+    }
+
+    /**
+     * @see androidx.media.filterfw.Filter#onProcess()
+     */
+    @Override
+    protected void onProcess() {
+        FrameValue arrayFrame = getConnectedInputPort("array").pullFrame().asFrameValues();
+        float[] array = (float[]) arrayFrame.getValue();
+        String outstr = Arrays.toString(array);
+
+        OutputPort outPort = getConnectedOutputPort("string");
+        FrameValue stringFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        stringFrame.setValue(outstr);
+        outPort.pushFrame(stringFrame);
+
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/IfElseFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/IfElseFilter.java
new file mode 100644
index 0000000..9553b75
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/IfElseFilter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.util.Log;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameBuffer2D;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+
+public class IfElseFilter extends Filter {
+
+    private static final String TAG = "IfElseFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public IfElseFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType videoIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+        FrameType imageOut = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.WRITE_GPU);
+
+        return new Signature().addInputPort("falseResult", Signature.PORT_REQUIRED, imageIn)
+                .addInputPort("trueResult", Signature.PORT_REQUIRED, videoIn)
+                .addInputPort("condition", Signature.PORT_REQUIRED, FrameType.single(boolean.class))
+                .addOutputPort("output", Signature.PORT_REQUIRED, imageOut)
+                .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("output");
+        FrameImage2D trueFrame = getConnectedInputPort("trueResult").pullFrame().asFrameImage2D();
+        FrameImage2D falseFrame = getConnectedInputPort("falseResult").pullFrame().asFrameImage2D();
+        FrameValue boolFrameValue = getConnectedInputPort("condition").pullFrame().asFrameValue();
+        boolean condition = (Boolean) boolFrameValue.getValue();
+        FrameBuffer2D outputFrame;
+        // If the condition is true, then we want to use the camera, else use the gallery
+        if (condition) {
+            outputFrame = trueFrame;
+        } else {
+            outputFrame = falseFrame;
+        }
+        outPort.pushFrame(outputFrame);
+
+    }
+
+}
diff --git a/tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageConstants.java
similarity index 60%
copy from tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java
copy to tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageConstants.java
index 7bb4c35..cfae8fc 100644
--- a/tests/BrowserTestPlugin/src/com/android/testplugin/TestPlugin.java
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright 2013 The Android Open 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,18 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.testplugin;
+package androidx.media.filterfw.samples.simplecamera;
 
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
+public class ImageConstants {
 
-public class TestPlugin extends Service {
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
+    public static final int MAX_BYTE = 255;
+    public static final int RED_OFFSET = 0;
+    public static final int GREEN_OFFSET = 1;
+    public static final int BLUE_OFFSET = 2;
+    public static final int PIX_CHANNELS = 4;
 }
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilter.java
new file mode 100644
index 0000000..14ec762
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilter.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Takes sharpness score, rates the image good if above 10, bad otherwise
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.ImageView;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public class ImageGoodnessFilter extends Filter {
+
+    private static final String TAG = "ImageGoodnessFilter";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final static String GREAT = "Great Picture!";
+    private final static String GOOD = "Good Picture!";
+    private final static String OK = "Ok Picture";
+    private final static String BAD = "Bad Picture";
+    private final static String AWFUL = "Awful Picture";
+    private final static float SMALL_SCORE_INC = 0.25f;
+    private final static float BIG_SCORE_INC = 0.5f;
+    private final static float LOW_VARIANCE = 0.1f;
+    private final static float MEDIUM_VARIANCE = 10;
+    private final static float HIGH_VARIANCE = 100;
+    private float sharpnessMean = 0;
+    private float sharpnessVar = 0;
+    private float underExposureMean = 0;
+    private float underExposureVar = 0;
+    private float overExposureMean = 0;
+    private float overExposureVar = 0;
+    private float contrastMean = 0;
+    private float contrastVar = 0;
+    private float colorfulnessMean = 0;
+    private float colorfulnessVar = 0;
+    private float brightnessMean = 0;
+    private float brightnessVar = 0;
+
+    private float motionMean = 0;
+    private float scoreMean = 0;
+    private static final float DECAY = 0.03f;
+    /**
+     * @param context
+     * @param name
+     */
+    public ImageGoodnessFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        FrameType floatT = FrameType.single(float.class);
+        FrameType imageIn = FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_GPU);
+
+        return new Signature()
+                .addInputPort("sharpness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("overExposure", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("underExposure", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("colorfulness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("contrastRating", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("motionValues", Signature.PORT_REQUIRED, FrameType.array(float.class))
+                .addInputPort("brightness", Signature.PORT_REQUIRED, floatT)
+                .addInputPort("capturing", Signature.PORT_REQUIRED, FrameType.single(boolean.class))
+                .addInputPort("image", Signature.PORT_REQUIRED, imageIn)
+                .addOutputPort("goodOrBadPic", Signature.PORT_REQUIRED,
+                        FrameType.single(String.class))
+                .addOutputPort("score", Signature.PORT_OPTIONAL, floatT)
+                .disallowOtherPorts();
+    }
+
+    /**
+     * @see androidx.media.filterfw.Filter#onProcess()
+     */
+    @Override
+    protected void onProcess() {
+        FrameValue sharpnessFrameValue =
+                getConnectedInputPort("sharpness").pullFrame().asFrameValue();
+        float sharpness = ((Float)sharpnessFrameValue.getValue()).floatValue();
+
+        FrameValue overExposureFrameValue =
+                getConnectedInputPort("overExposure").pullFrame().asFrameValue();
+        float overExposure = ((Float)overExposureFrameValue.getValue()).floatValue();
+
+        FrameValue underExposureFrameValue =
+                getConnectedInputPort("underExposure").pullFrame().asFrameValue();
+        float underExposure = ((Float)underExposureFrameValue.getValue()).floatValue();
+
+        FrameValue colorfulnessFrameValue =
+                getConnectedInputPort("colorfulness").pullFrame().asFrameValue();
+        float colorfulness = ((Float)colorfulnessFrameValue.getValue()).floatValue();
+
+        FrameValue contrastRatingFrameValue =
+                getConnectedInputPort("contrastRating").pullFrame().asFrameValue();
+        float contrastRating = ((Float)contrastRatingFrameValue.getValue()).floatValue();
+
+        FrameValue brightnessFrameValue =
+                getConnectedInputPort("brightness").pullFrame().asFrameValue();
+        float brightness = ((Float)brightnessFrameValue.getValue()).floatValue();
+
+        FrameValue motionValuesFrameValue =
+                getConnectedInputPort("motionValues").pullFrame().asFrameValue();
+        float[] motionValues = (float[]) motionValuesFrameValue.getValue();
+
+
+        float vectorAccel = (float) Math.sqrt(Math.pow(motionValues[0], 2) +
+                Math.pow(motionValues[1], 2) + Math.pow(motionValues[2], 2));
+        String outStr;
+
+        FrameValue capturingFrameValue =
+                getConnectedInputPort("capturing").pullFrame().asFrameValue();
+        boolean capturing = (Boolean) capturingFrameValue.getValue();
+
+        FrameImage2D inputImage = getConnectedInputPort("image").pullFrame().asFrameImage2D();
+
+
+        // TODO: get rid of magic numbers
+        float score = 0.0f;
+        score = computePictureScore(vectorAccel, sharpness, underExposure, overExposure,
+                    contrastRating, colorfulness, brightness);
+        if (scoreMean == 0) scoreMean = score;
+        else scoreMean = scoreMean * (1 - DECAY) + score * DECAY;
+
+        if (motionMean == 0) motionMean = vectorAccel;
+        else motionMean = motionMean * (1 - DECAY) + vectorAccel * DECAY;
+
+        float classifierScore = classifierComputeScore(vectorAccel, sharpness, underExposure,
+                colorfulness, contrastRating, score);
+
+//        Log.v(TAG, "ClassifierScore:: " + classifierScore);
+        final float GREAT_SCORE = 3.5f;
+        final float GOOD_SCORE = 2.5f;
+        final float OK_SCORE = 1.5f;
+        final float BAD_SCORE = 0.5f;
+
+        if (score >= GREAT_SCORE) {
+            outStr = GREAT;
+        } else if (score >= GOOD_SCORE) {
+            outStr = GOOD;
+        } else if (score >= OK_SCORE) {
+            outStr = OK;
+        } else if (score >= BAD_SCORE) {
+            outStr = BAD;
+        } else {
+            outStr = AWFUL;
+        }
+
+        if(capturing) {
+            if (outStr.equals(GREAT)) {
+                // take a picture
+                Bitmap bitmap = inputImage.toBitmap();
+
+                new AsyncOperation().execute(bitmap);
+                final float RESET_FEATURES = 0.01f;
+                sharpnessMean = RESET_FEATURES;
+                underExposureMean = RESET_FEATURES;
+                overExposureMean = RESET_FEATURES;
+                contrastMean = RESET_FEATURES;
+                colorfulnessMean = RESET_FEATURES;
+                brightnessMean = RESET_FEATURES;
+            }
+        }
+
+        OutputPort outPort = getConnectedOutputPort("goodOrBadPic");
+        FrameValue stringFrame = outPort.fetchAvailableFrame(null).asFrameValue();
+        stringFrame.setValue(outStr);
+        outPort.pushFrame(stringFrame);
+
+        OutputPort scoreOutPort = getConnectedOutputPort("score");
+        FrameValue scoreFrame = scoreOutPort.fetchAvailableFrame(null).asFrameValue();
+        scoreFrame.setValue(score);
+        scoreOutPort.pushFrame(scoreFrame);
+
+    }
+
+    private class AsyncOperation extends AsyncTask<Bitmap, Void, String> {
+        private Bitmap b;
+        protected void onPostExecute(String result) {
+            ImageView view = SmartCamera.getImageView();
+            view.setImageBitmap(b);
+        }
+
+        @Override
+        protected String doInBackground(Bitmap... params) {
+            // TODO Auto-generated method stub
+            b = params[0];
+            return null;
+        }
+
+    }
+    // Returns a number between -1 and 1
+    private float classifierComputeScore(float vectorAccel, float sharpness, float underExposure,
+           float colorfulness, float contrast, float score) {
+        float result = (-0.0223f * sharpness + -0.0563f * underExposure + 0.0137f * colorfulness
+                + 0.3102f * contrast + 0.0314f * vectorAccel + -0.0094f * score + 0.0227f *
+                sharpnessMean + 0.0459f * underExposureMean + -0.3934f * contrastMean +
+                -0.0697f * motionMean + 0.0091f * scoreMean + -0.0152f);
+        return result;
+    }
+
+    // Returns a number between -1 and 4 representing the score for this picture
+    private float computePictureScore(float vector_accel, float sharpness,
+            float underExposure, float overExposure, float contrastRating, float colorfulness,
+            float brightness) {
+        final float ACCELERATION_THRESHOLD_VERY_STEADY = 0.1f;
+        final float ACCELERATION_THRESHOLD_STEADY = 0.3f;
+        final float ACCELERATION_THRESHOLD_MOTION = 2f;
+
+        float score = 0.0f;
+        if (vector_accel > ACCELERATION_THRESHOLD_MOTION) {
+            score -= (BIG_SCORE_INC + BIG_SCORE_INC); // set score to -1, bad pic
+        } else if (vector_accel > ACCELERATION_THRESHOLD_STEADY) {
+            score -= BIG_SCORE_INC;
+            score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
+                    colorfulness, brightness, score);
+        } else if (vector_accel < ACCELERATION_THRESHOLD_VERY_STEADY) {
+            score += BIG_SCORE_INC;
+            score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
+                    colorfulness, brightness, score);
+        } else {
+            score = subComputeScore(sharpness, underExposure, overExposure, contrastRating,
+                    colorfulness, brightness, score);
+        }
+        return score;
+    }
+
+    // Changes the score by at most +/- 3.5
+    private float subComputeScore(float sharpness, float underExposure, float overExposure,
+                float contrastRating, float colorfulness, float brightness, float score) {
+        // The score methods return values -0.5 to 0.5
+        final float SHARPNESS_WEIGHT = 2;
+        score += SHARPNESS_WEIGHT * sharpnessScore(sharpness);
+        score += underExposureScore(underExposure);
+        score += overExposureScore(overExposure);
+        score += contrastScore(contrastRating);
+        score += colorfulnessScore(colorfulness);
+        score += brightnessScore(brightness);
+        return score;
+    }
+
+    private float sharpnessScore(float sharpness) {
+        if (sharpnessMean == 0) {
+            sharpnessMean = sharpness;
+            sharpnessVar = 0;
+            return 0;
+        } else {
+            sharpnessMean = sharpnessMean * (1 - DECAY) + sharpness * DECAY;
+            sharpnessVar = sharpnessVar * (1 - DECAY) + (sharpness - sharpnessMean) *
+                    (sharpness - sharpnessMean) * DECAY;
+            if (sharpnessVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (sharpness < sharpnessMean && sharpnessVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (sharpness < sharpnessMean) {
+                return -SMALL_SCORE_INC;
+            } else if (sharpness > sharpnessMean && sharpnessVar > HIGH_VARIANCE) {
+                return 0;
+            } else if (sharpness > sharpnessMean && sharpnessVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else  {
+                return BIG_SCORE_INC; // low variance, sharpness above the mean
+            }
+        }
+    }
+
+    private float underExposureScore(float underExposure) {
+        if (underExposureMean == 0) {
+            underExposureMean = underExposure;
+            underExposureVar = 0;
+            return 0;
+        } else {
+            underExposureMean = underExposureMean * (1 - DECAY) + underExposure * DECAY;
+            underExposureVar = underExposureVar * (1 - DECAY) + (underExposure - underExposureMean)
+                    * (underExposure - underExposureMean) * DECAY;
+            if (underExposureVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (underExposure > underExposureMean && underExposureVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (underExposure > underExposureMean) {
+                return -SMALL_SCORE_INC;
+            } else if (underExposure < underExposureMean && underExposureVar > HIGH_VARIANCE) {
+                return 0;
+            } else if (underExposure < underExposureMean && underExposureVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else {
+                return BIG_SCORE_INC; // low variance, underExposure below the mean
+            }
+        }
+    }
+
+    private float overExposureScore(float overExposure) {
+        if (overExposureMean == 0) {
+            overExposureMean = overExposure;
+            overExposureVar = 0;
+            return 0;
+        } else {
+            overExposureMean = overExposureMean * (1 - DECAY) + overExposure * DECAY;
+            overExposureVar = overExposureVar * (1 - DECAY) + (overExposure - overExposureMean) *
+                    (overExposure - overExposureMean) * DECAY;
+            if (overExposureVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (overExposure > overExposureMean && overExposureVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (overExposure > overExposureMean) {
+                return -SMALL_SCORE_INC;
+            } else if (overExposure < overExposureMean && overExposureVar > HIGH_VARIANCE) {
+                return 0;
+            } else if (overExposure < overExposureMean && overExposureVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else {
+                return BIG_SCORE_INC; // low variance, overExposure below the mean
+            }
+        }
+    }
+
+    private float contrastScore(float contrast) {
+        if (contrastMean == 0) {
+            contrastMean = contrast;
+            contrastVar = 0;
+            return 0;
+        } else {
+            contrastMean = contrastMean * (1 - DECAY) + contrast * DECAY;
+            contrastVar = contrastVar * (1 - DECAY) + (contrast - contrastMean) *
+                    (contrast - contrastMean) * DECAY;
+            if (contrastVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (contrast < contrastMean && contrastVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (contrast < contrastMean) {
+                return -SMALL_SCORE_INC;
+            } else if (contrast > contrastMean && contrastVar > 100) {
+                return 0;
+            } else if (contrast > contrastMean && contrastVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else {
+                return BIG_SCORE_INC; // low variance, contrast above the mean
+            }
+        }
+    }
+
+    private float colorfulnessScore(float colorfulness) {
+        if (colorfulnessMean == 0) {
+            colorfulnessMean = colorfulness;
+            colorfulnessVar = 0;
+            return 0;
+        } else {
+            colorfulnessMean = colorfulnessMean * (1 - DECAY) + colorfulness * DECAY;
+            colorfulnessVar = colorfulnessVar * (1 - DECAY) + (colorfulness - colorfulnessMean) *
+                    (colorfulness - colorfulnessMean) * DECAY;
+            if (colorfulnessVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (colorfulness < colorfulnessMean && colorfulnessVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (colorfulness < colorfulnessMean) {
+                return -SMALL_SCORE_INC;
+            } else if (colorfulness > colorfulnessMean && colorfulnessVar > 100) {
+                return 0;
+            } else if (colorfulness > colorfulnessMean && colorfulnessVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else {
+                return BIG_SCORE_INC; // low variance, colorfulness above the mean
+            }
+        }
+    }
+
+    private float brightnessScore(float brightness) {
+        if (brightnessMean == 0) {
+            brightnessMean = brightness;
+            brightnessVar = 0;
+            return 0;
+        } else {
+            brightnessMean = brightnessMean * (1 - DECAY) + brightness * DECAY;
+            brightnessVar = brightnessVar * (1 - DECAY) + (brightness - brightnessMean) *
+                    (brightness - brightnessMean) * DECAY;
+            if (brightnessVar < LOW_VARIANCE) {
+                return BIG_SCORE_INC;
+            } else if (brightness < brightnessMean && brightnessVar > MEDIUM_VARIANCE) {
+                return -BIG_SCORE_INC;
+            } else if (brightness < brightnessMean) {
+                return -SMALL_SCORE_INC;
+            } else if (brightness > brightnessMean && brightnessVar > 100) {
+                return 0;
+            } else if (brightness > brightnessMean && brightnessVar > MEDIUM_VARIANCE) {
+                return SMALL_SCORE_INC;
+            } else {
+                return BIG_SCORE_INC; // low variance, brightness above the mean
+            }
+        }
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/MotionSensorWTime.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/MotionSensorWTime.java
new file mode 100644
index 0000000..64f3ef3
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/MotionSensorWTime.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Make values from a motion sensor (e.g., accelerometer) available as filter outputs.
+
+package androidx.media.filterfw.samples.simplecamera;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Log;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.FrameValues;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+public final class MotionSensorWTime extends Filter implements SensorEventListener {
+
+    private SensorManager mSensorManager = null;
+    private Sensor mSensor = null;
+
+    private float[] mValues = new float[3];
+    private float[][] mTemp = new float[3][3];
+    private float[] mAvgValues = new float[3];
+    private int mCounter = 0;
+
+    public MotionSensorWTime(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addOutputPort("values", Signature.PORT_REQUIRED, FrameType.array(float.class))
+            .addOutputPort("timestamp", Signature.PORT_OPTIONAL, FrameType.single(long.class))
+            .disallowOtherPorts();
+    }
+
+    @Override
+    protected void onPrepare() {
+        mSensorManager = (SensorManager)getContext().getApplicationContext()
+                            .getSystemService(Context.SENSOR_SERVICE);
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
+        // TODO: currently, the type of sensor is hardcoded. Should be able to set the sensor
+        //  type as filter input!
+        mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_UI);
+    }
+
+    @Override
+    protected void onTearDown() {
+        mSensorManager.unregisterListener(this);
+    }
+
+    @Override
+    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // (Do we need to do something when sensor accuracy changes?)
+    }
+
+    @Override
+    public final void onSensorChanged(SensorEvent event) {
+        synchronized(mValues) {
+            mValues[0] = event.values[0];
+            mValues[1] = event.values[1];
+            mValues[2] = event.values[2];
+        }
+    }
+
+    @Override
+    protected void onProcess() {
+        OutputPort outPort = getConnectedOutputPort("values");
+        FrameValues outFrame = outPort.fetchAvailableFrame(null).asFrameValues();
+        synchronized(mValues) {
+            if (mCounter < 3 && mCounter >= 0) {
+                mTemp[0][mCounter] = mValues[0];
+                mTemp[1][mCounter] = mValues[1];
+                mTemp[2][mCounter] = mValues[2];
+            }
+
+            mCounter = (mCounter + 1) % 3;
+
+            mAvgValues[0] = (mTemp[0][0] + mTemp[0][1] + mTemp[0][2]) / 3;
+            mAvgValues[1] = (mTemp[1][0] + mTemp[1][1] + mTemp[1][2]) / 3;
+            mAvgValues[2] = (mTemp[2][0] + mTemp[2][1] + mTemp[2][2]) / 3;
+            outFrame.setValues(mAvgValues);
+        }
+        outFrame.setTimestamp(System.currentTimeMillis() * 1000000L);
+        outPort.pushFrame(outFrame);
+
+        OutputPort timeOutPort = getConnectedOutputPort("timestamp");
+        if (timeOutPort != null) {
+            long timestamp = System.nanoTime();
+            Log.v("MotionSensor", "Timestamp is: " + timestamp);
+            FrameValue timeStampFrame = timeOutPort.fetchAvailableFrame(null).asFrameValue();
+            timeStampFrame.setValue(timestamp);
+            timeOutPort.pushFrame(timeStampFrame);
+        }
+    }
+}
+
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/SmartCamera.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/SmartCamera.java
new file mode 100644
index 0000000..ba0333a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/SmartCamera.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import androidx.media.filterfw.FilterGraph;
+import androidx.media.filterfw.GraphReader;
+import androidx.media.filterfw.GraphRunner;
+import androidx.media.filterfw.MffContext;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+
+public class SmartCamera extends Activity {
+
+    private SurfaceView mCameraView;
+    private TextView mGoodBadTextView;
+    private TextView mFPSTextView;
+    private TextView mEyesTextView;
+    private TextView mSmilesTextView;
+    private TextView mScoreTextView;
+    private static ImageView mImageView1;
+    private static ImageView mImageView2;
+    private static ImageView mImageView3;
+    private static ImageView mImageView4;
+    private static ImageView mImageView5;
+    private Button mStartStopButton;
+    private TextView mImagesSavedTextView;
+    private Spinner mSpinner;
+    private LinearLayout mLinearLayout;
+
+    private MffContext mContext;
+    private FilterGraph mGraph;
+    private GraphRunner mRunner;
+    private Handler mHandler = new Handler();
+
+    private static final String TAG = "SmartCamera";
+    private static final boolean sUseFacialExpression = false;
+    private boolean isPendingRunGraph = false;
+
+    private static ArrayList<ImageView> mImages;
+    private static int count = -1;
+    private static boolean countHasReachedMax = false;
+    private static int numImages = 0;
+
+    // Function to return the correct image view to display the current bitmap
+    public static ImageView getImageView() {
+        if (count == numImages-1) countHasReachedMax = true;
+        count = (count+1) % numImages;
+        return mImages.get(count);
+    }
+
+    // Function used to run images through the graph, mainly for CSV data generation
+    public void runGraphOnImage(String filePath, String fileName) {
+        if(fileName.endsWith(".jpg") == false) {
+            return;
+        }
+        mGraph.getVariable("gallerySource").setValue(filePath + "/" + fileName);
+        Log.v(TAG, "runGraphOnImage : : " + filePath + " name: " + fileName);
+        mGraph.getVariable("imageName").setValue(fileName);
+        mGraph.getVariable("filePath").setValue(filePath); // wrong
+        try {
+            Thread.sleep(400);
+        } catch (InterruptedException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    // Function to clear the "Images Saved" text off the screen
+    private void clearImagesSavedTextView() {
+        mImagesSavedTextView.setText("");
+    }
+
+    // Function to capture the images in the current imageviews and save them to the gallery
+    private void captureImages() {
+        ((WaveTriggerFilter) mGraph.getFilter("snapEffect")).trigger();
+        mGraph.getVariable("startCapture").setValue(false);
+        Bitmap bitmap = null;
+        Drawable res = getResources().getDrawable(R.drawable.black_screen);
+        Calendar cal = Calendar.getInstance();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
+
+        Log.v(TAG, "numImages: " + numImages + " count: " + count +
+                " hasReachedMax: " + countHasReachedMax);
+        int maxI = countHasReachedMax ? numImages : count+1;
+        if(maxI != 0) {
+            if (maxI == 1) mImagesSavedTextView.setText("Image Saved");
+            else {
+                mImagesSavedTextView.setText("" + maxI + " Images Saved");
+            }
+        }
+        for (int i = 0; i < maxI; i++) {
+            bitmap = ((BitmapDrawable)mImages.get(i).getDrawable()).getBitmap();
+            mImages.get(i).setImageDrawable(res);
+            MediaStore.Images.Media.insertImage(getContentResolver(), bitmap,
+                    sdf.format(cal.getTime()) + "_image" + i + ".jpg", "image " + i);
+        }
+        mStartStopButton.setText("Start");
+        count = -1;
+        countHasReachedMax = false;
+        mSpinner.setEnabled(true);
+        mHandler.postDelayed(new Runnable() {
+            public void run() {
+                clearImagesSavedTextView();
+            }
+        }, 5000);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.simplecamera);
+        setTitle("Smart Camera");
+
+        mContext = new MffContext(this);
+
+        mCameraView = (SurfaceView) findViewById(R.id.cameraView);
+        mGoodBadTextView = (TextView) findViewById(R.id.goodOrBadTextView);
+        mFPSTextView = (TextView) findViewById(R.id.fpsTextView);
+        mScoreTextView = (TextView) findViewById(R.id.scoreTextView);
+        mStartStopButton = (Button) findViewById(R.id.startButton);
+        mImagesSavedTextView = (TextView) findViewById(R.id.imagesSavedTextView);
+        mImagesSavedTextView.setText("");
+        mSpinner = (Spinner) findViewById(R.id.spinner);
+        mLinearLayout = (LinearLayout) findViewById(R.id.scrollViewLinearLayout);
+        mImages = new ArrayList<ImageView>();
+
+        // Spinner is used to determine how many image views are displayed at the bottom
+        // of the screen. Based on the item position that is selected, we inflate that
+        // many imageviews into the bottom linear layout.
+        mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parentView, View selectedItemView,
+                    int position, long id) {
+                mLinearLayout.removeViews(0,numImages);
+                numImages = position+1;
+                mImages.clear();
+                LayoutInflater inflater = getLayoutInflater();
+                for (int i = 0; i < numImages; i++) {
+                    ImageView tmp = (ImageView) inflater.inflate(R.layout.imageview, null);
+                    mImages.add(tmp);
+                    mLinearLayout.addView(tmp);
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parentView) {
+            }
+        });
+
+        numImages = mSpinner.getSelectedItemPosition()+1;
+        mImages.clear();
+        LayoutInflater inflater = getLayoutInflater();
+        for (int i = 0; i < numImages; i++) {
+            ImageView tmp = (ImageView) inflater.inflate(R.layout.imageview, null);
+            mImages.add(tmp);
+            mLinearLayout.addView(tmp);
+
+        }
+
+        // Button used to start and stop the capture of images when they are deemed great
+        mStartStopButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mStartStopButton.getText().equals("Start")) {
+                    mGraph.getVariable("startCapture").setValue(true);
+                    mStartStopButton.setText("Stop");
+                    mSpinner.setEnabled(false);
+                } else {
+                    boolean tmp = (Boolean) mGraph.getVariable("startCapture").getValue();
+                    if (tmp == false) {
+                        return;
+                    }
+                    if (count == numImages-1) countHasReachedMax = true;
+                    captureImages();
+                }
+            }
+        });
+
+        // Button to open the gallery to show the images in there
+        Button galleryOpen = (Button) findViewById(R.id.galleryOpenButton);
+        galleryOpen.setOnClickListener(new OnClickListener() {
+           @Override
+           public void onClick(View v) {
+               Intent openGalleryIntent = new Intent(Intent.ACTION_MAIN);
+               openGalleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
+               startActivity(openGalleryIntent);
+           }
+        });
+
+        loadGraph();
+        mGraph.getVariable("startCapture").setValue(false);
+        runGraph();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        Log.i(TAG, "onPause");
+        if (mContext != null) {
+            mContext.onPause();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.i(TAG, "onResume");
+        if (mContext != null) {
+            mContext.onResume();
+        }
+        if (isPendingRunGraph) {
+            isPendingRunGraph = false;
+            runGraph();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        Log.i(TAG, "onStop");
+    }
+
+    // Build the Filtergraph for Camera
+    private void loadGraph() {
+        try {
+            mGraph = GraphReader.readXmlGraphResource(mContext, R.raw.camera_graph);
+            mRunner = mGraph.getRunner();
+
+            // Connect views
+            mGraph.bindFilterToView("camViewTarget", mCameraView);
+            mGraph.bindFilterToView("goodOrBadTextView", mGoodBadTextView);
+            mGraph.bindFilterToView("fpsTextView", mFPSTextView);
+            mGraph.bindFilterToView("scoreTextView", mScoreTextView);
+
+            // Used for Facial Expressions
+            if (sUseFacialExpression) {
+                mGraph.bindFilterToView("eyesTextView", mEyesTextView);
+                mGraph.bindFilterToView("smilesTextView", mSmilesTextView);
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    // Asynchronously run the filtergraph
+    private void runGraph() {
+        mRunner.setIsVerbose(true);
+        mRunner.start(mGraph);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/WaveTriggerFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/WaveTriggerFilter.java
new file mode 100644
index 0000000..9f72940
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/samples/simplecamera/WaveTriggerFilter.java
@@ -0,0 +1,83 @@
+/*
+ * 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.InputPort;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.OutputPort;
+import androidx.media.filterfw.Signature;
+
+// The Filter is used to generate the camera snap effect.
+// The effect is to give the image a sudden white appearance.
+public final class WaveTriggerFilter extends Filter {
+
+    private boolean mTrigger = false;
+    private boolean mInWaveMode = false;
+    private float mTime = 0f;
+
+    public WaveTriggerFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+            .addOutputPort("value", Signature.PORT_REQUIRED, FrameType.single())
+            .disallowOtherPorts();
+    }
+
+    public synchronized void trigger() {
+        mTrigger = true;
+    }
+
+    @Override
+    public void onInputPortOpen(InputPort port) {
+        if (port.getName().equals("trigger")) {
+            port.bindToFieldNamed("mTrigger");
+            port.setAutoPullEnabled(true);
+        }
+    }
+
+    @Override
+    protected synchronized void onProcess() {
+        // Check if we were triggered
+        if (mTrigger) {
+            mInWaveMode = true;
+            mTrigger = false;
+            mTime = 0.5f;
+        }
+
+        // Calculate output value
+        float value = 0.5f;
+        if (mInWaveMode) {
+            value = -Math.abs(mTime - 1f) + 1f;
+            mTime += 0.2f;
+            if (mTime >= 2f) {
+                mInWaveMode = false;
+            }
+        }
+
+        // Push Value
+        OutputPort outPort = getConnectedOutputPort("value");
+        FrameValue frame = outPort.fetchAvailableFrame(null).asFrameValue();
+        frame.setValue(value);
+        outPort.pushFrame(frame);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk
new file mode 100644
index 0000000..50926a6
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := SmartCamera-tests
+
+LOCAL_SRC_FILES += $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+#LOCAL_STATIC_JAVA_LIBRARIES := filterframework-test-lib
+LOCAL_STATIC_JAVA_LIBRARIES += guava
+
+#LOCAL_JAVA_LIBRARIES := filterframework-test-lib
+LOCAL_STATIC_JAVA_LIBRARIES := guava
+
+LOCAL_STATIC_JAVA_LIBRARIES +=
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_INSTRUMENTATION_FOR := SmartCamera
+
+include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/AndroidManifest.xml b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/AndroidManifest.xml
new file mode 100644
index 0000000..3363af4
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="androidx.media.filterfw.samples.simplecamera.tests"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" />
+
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="androidx.media.filterfw.samples.simplecamera" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/project.properties b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/project.properties
new file mode 100644
index 0000000..4653837a
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/project.properties
@@ -0,0 +1,16 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library.reference.1=../filterfw-test-lib
+android.library.reference.2=../filterfw
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/res/.README b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/res/.README
new file mode 100644
index 0000000..c29cd87
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/res/.README
@@ -0,0 +1,3 @@
+The res directory is needed for Eclipse to correctly build the project, but it
+is not possible to check in a directory into git. This file guarantees the res
+directory exists.
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameSourceFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameSourceFilter.java
new file mode 100644
index 0000000..7daa03f
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameSourceFilter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * A {@link Filter} that pushes out externally injected frames.
+ * <p> When a frame is injected using {@link #injectFrame(Frame)}, this source will push it on its
+ * output port and then sleep until another frame is injected.
+ * <p> Multiple frames may be injected before any frame is pushed out. In this case they will be
+ * queued and pushed in FIFO order.
+ */
+class FrameSourceFilter extends Filter {
+
+    private final Queue<Frame> mFrames = new LinkedList<Frame>();
+
+    FrameSourceFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+                .addOutputPort("output", Signature.PORT_REQUIRED, FrameType.any())
+                .disallowOtherPorts();
+    }
+
+    private synchronized Frame obtainFrame() {
+        if (mFrames.isEmpty()) {
+            enterSleepState();
+            return null;
+        } else {
+            return mFrames.poll();
+        }
+    }
+
+    /**
+     * Call this method to inject a frame that will be pushed in a future execution of the filter.
+     * <p> If multiple frames are injected then they will be pushed one per execution in FIFO order.
+     */
+    public synchronized void injectFrame(Frame frame) {
+        mFrames.add(frame);
+        wakeUp();
+    }
+
+    @Override
+    protected void onProcess() {
+        Frame frame = obtainFrame();
+        if (frame != null) {
+            getConnectedOutputPort("output").pushFrame(frame);
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameTargetFilter.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameTargetFilter.java
new file mode 100644
index 0000000..1f0e267
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/FrameTargetFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+/**
+ * A {@link Filter} that consumes frames and allows to register a listener to observe when
+ * a new frame has been consumed.
+ */
+class FrameTargetFilter extends Filter {
+
+    interface Listener {
+        /**
+         * Called each time this filter receives a new frame. The implementer of this method is
+         * responsible for releasing the frame.
+         */
+        void onFramePushed(String filterName, Frame frame);
+    }
+
+    private Listener mListener;
+
+    FrameTargetFilter(MffContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public Signature getSignature() {
+        return new Signature()
+                .addInputPort("input", Signature.PORT_REQUIRED, FrameType.any())
+                .disallowOtherPorts();
+    }
+
+    public synchronized void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    protected synchronized void onProcess() {
+        Frame frame = getConnectedInputPort("input").pullFrame();
+        if (mListener != null) {
+            frame.retain();
+            mListener.onFramePushed(getName(), frame);
+        }
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffFilterTestCase.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffFilterTestCase.java
new file mode 100644
index 0000000..84efd28
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffFilterTestCase.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+import androidx.media.filterfw.GraphRunner.Listener;
+import androidx.media.filterfw.Signature.PortInfo;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A {@link TestCase} for testing single MFF filter runs. Implementers should extend this class and
+ * implement the {@link #createFilter(MffContext)} method to create the filter under test. Inside
+ * each test method, the implementer should supply one or more frames for all the filter inputs
+ * (calling {@link #injectInputFrame(String, Frame)}) and then invoke {@link #process()}. Once the
+ * processing finishes, one should call {@link #getOutputFrame(String)} to get and inspect the
+ * output frames.
+ *
+ * TODO: extend this to deal with filters that push multiple output frames.
+ * TODO: relax the requirement that all output ports should be pushed (the implementer should be
+ *       able to tell which ports to wait for before process() returns).
+ * TODO: handle undeclared inputs and outputs.
+ */
+public abstract class MffFilterTestCase extends MffTestCase {
+
+    private static final long DEFAULT_TIMEOUT_MS = 1000;
+
+    private FilterGraph mGraph;
+    private GraphRunner mRunner;
+    private Map<String, Frame> mOutputFrames;
+    private Set<String> mEmptyOutputPorts;
+
+    private SettableFuture<Void> mProcessResult;
+
+    protected abstract Filter createFilter(MffContext mffContext);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        MffContext mffContext = getMffContext();
+        FilterGraph.Builder graphBuilder = new FilterGraph.Builder(mffContext);
+        Filter filterUnderTest = createFilter(mffContext);
+        graphBuilder.addFilter(filterUnderTest);
+
+        connectInputPorts(mffContext, graphBuilder, filterUnderTest);
+        connectOutputPorts(mffContext, graphBuilder, filterUnderTest);
+
+        mGraph = graphBuilder.build();
+        mRunner = mGraph.getRunner();
+        mRunner.setListener(new Listener() {
+            @Override
+            public void onGraphRunnerStopped(GraphRunner runner) {
+                mProcessResult.set(null);
+            }
+
+            @Override
+            public void onGraphRunnerError(Exception exception, boolean closedSuccessfully) {
+                mProcessResult.setException(exception);
+            }
+        });
+
+        mOutputFrames = new HashMap<String, Frame>();
+        mProcessResult = SettableFuture.create();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (Frame frame : mOutputFrames.values()) {
+            frame.release();
+        }
+        mOutputFrames = null;
+
+        mRunner.stop();
+        mRunner = null;
+        mGraph = null;
+
+        mProcessResult = null;
+        super.tearDown();
+    }
+
+    protected void injectInputFrame(String portName, Frame frame) {
+        FrameSourceFilter filter = (FrameSourceFilter) mGraph.getFilter("in_" + portName);
+        filter.injectFrame(frame);
+    }
+
+    /**
+     * Returns the frame pushed out by the filter under test. Should only be called after
+     * {@link #process(long)} has returned.
+     */
+    protected Frame getOutputFrame(String outputPortName) {
+        return mOutputFrames.get("out_" + outputPortName);
+    }
+
+    protected void process(long timeoutMs)
+            throws ExecutionException, TimeoutException, InterruptedException {
+        mRunner.start(mGraph);
+        mProcessResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    protected void process() throws ExecutionException, TimeoutException, InterruptedException {
+        process(DEFAULT_TIMEOUT_MS);
+    }
+
+    /**
+     * This method should be called to create the input frames inside the test cases (instead of
+     * {@link Frame#create(FrameType, int[])}). This is required to work around a requirement for
+     * the latter method to be called on the MFF thread.
+     */
+    protected Frame createFrame(FrameType type, int[] dimensions) {
+        return new Frame(type, dimensions, mRunner.getFrameManager());
+    }
+
+    private void connectInputPorts(
+            MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter) {
+        Signature signature = filter.getSignature();
+        for (Entry<String, PortInfo> inputPortEntry : signature.getInputPorts().entrySet()) {
+            Filter inputFilter = new FrameSourceFilter(mffContext, "in_" + inputPortEntry.getKey());
+            graphBuilder.addFilter(inputFilter);
+            graphBuilder.connect(inputFilter, "output", filter, inputPortEntry.getKey());
+        }
+    }
+
+    private void connectOutputPorts(
+            MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter) {
+        Signature signature = filter.getSignature();
+        mEmptyOutputPorts = new HashSet<String>();
+        OutputFrameListener outputFrameListener = new OutputFrameListener();
+        for (Entry<String, PortInfo> outputPortEntry : signature.getOutputPorts().entrySet()) {
+            FrameTargetFilter outputFilter = new FrameTargetFilter(
+                    mffContext, "out_" + outputPortEntry.getKey());
+            graphBuilder.addFilter(outputFilter);
+            graphBuilder.connect(filter, outputPortEntry.getKey(), outputFilter, "input");
+            outputFilter.setListener(outputFrameListener);
+            mEmptyOutputPorts.add("out_" + outputPortEntry.getKey());
+        }
+    }
+
+    private class OutputFrameListener implements FrameTargetFilter.Listener {
+
+        @Override
+        public void onFramePushed(String filterName, Frame frame) {
+            mOutputFrames.put(filterName, frame);
+            boolean alreadyPushed = !mEmptyOutputPorts.remove(filterName);
+            if (alreadyPushed) {
+                throw new IllegalStateException(
+                        "A frame has been pushed twice to the same output port.");
+            }
+            if (mEmptyOutputPorts.isEmpty()) {
+                // All outputs have been pushed, stop the graph.
+                mRunner.stop();
+            }
+        }
+
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffTestCase.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffTestCase.java
new file mode 100644
index 0000000..2f33a5c
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/MffTestCase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+
+import junit.framework.TestCase;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * A {@link TestCase} for testing objects requiring {@link MffContext}. This test case can only be
+ * used to test the functionality that does not rely on GL support and camera.
+ */
+public class MffTestCase extends AndroidTestCase {
+
+    private HandlerThread mMffContextHandlerThread;
+    private MffContext mMffContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // MffContext needs to be created on a separate thread to allow MFF to post Runnable's.
+        mMffContextHandlerThread = new HandlerThread("MffContextThread");
+        mMffContextHandlerThread.start();
+        Handler handler = new Handler(mMffContextHandlerThread.getLooper());
+        FutureTask<MffContext> task = new FutureTask<MffContext>(new Callable<MffContext>() {
+            @Override
+            public MffContext call() throws Exception {
+                MffContext.Config config = new MffContext.Config();
+                config.requireCamera = false;
+                config.requireOpenGL = false;
+                config.forceNoGL = true;
+                return new MffContext(getContext(), config);
+            }
+        });
+        handler.post(task);
+        // Wait for the context to be created on the handler thread.
+        mMffContext = task.get();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mMffContextHandlerThread.getLooper().quit();
+        mMffContextHandlerThread = null;
+        mMffContext.release();
+        mMffContext = null;
+        super.tearDown();
+    }
+
+    protected MffContext getMffContext() {
+        return mMffContext;
+    }
+
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AverageFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AverageFilterTest.java
new file mode 100644
index 0000000..37b5eb8
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AverageFilterTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+
+public class AverageFilterTest extends MffFilterTestCase {
+
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        return new AverageFilter(mffContext, "averageFilter");
+    }
+
+    public void testAverageFilter() throws Exception {
+        FrameValue frame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        frame.setValue(5f);
+
+        injectInputFrame("sharpness", frame);
+
+        process();
+        assertEquals(1f, ((Float) getOutputFrame("avg").asFrameValue().getValue()).floatValue(),
+                0.001f);
+    }
+
+    public void testAverageFilter2() throws Exception{
+        FrameValue frame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        frame.setValue(4f);
+
+        injectInputFrame("sharpness", frame);
+
+        process();
+        assertEquals(0.8f, ((Float) getOutputFrame("avg").asFrameValue().getValue()).floatValue(),
+                0.001f);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilterTest.java
new file mode 100644
index 0000000..3c8d127
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/AvgBrightnessFilterTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import android.net.Uri;
+import android.provider.MediaStore;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+
+public class AvgBrightnessFilterTest extends MffFilterTestCase {
+    private AssetManager assetMgr = null;
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        assetMgr = mffContext.getApplicationContext().getAssets();
+        return new AvgBrightnessFilter(mffContext, "brightnessFilter");
+    }
+
+    public void testBrightnessFilter() throws Exception{
+        final int INPUT_WIDTH = 480;
+        final int INPUT_HEIGHT = 640;
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {INPUT_WIDTH,INPUT_HEIGHT}).asFrameImage2D();
+
+        Bitmap bitmap = BitmapFactory.decodeStream(assetMgr.open("0002_000390.jpg"));
+        image.setBitmap(bitmap);
+
+        injectInputFrame("image", image);
+
+        process();
+        final float EXPECTED_RESULT = 0.35f;
+        assertEquals(EXPECTED_RESULT, ((Float) getOutputFrame("brightnessRating").
+                asFrameValue().getValue()).floatValue(), 0.01f);
+    }
+}
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilterTest.java
new file mode 100644
index 0000000..6072755
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ContrastRatioFilterTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+import androidx.media.filterfw.samples.simplecamera.ContrastRatioFilter;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+
+public class ContrastRatioFilterTest extends MffFilterTestCase {
+    private AssetManager assetMgr = null;
+
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        assetMgr = mffContext.getApplicationContext().getAssets();
+        return new ContrastRatioFilter(mffContext, "contrastFilter");
+    }
+
+    public void testContrastFilter() throws Exception {
+
+        final int INPUT_WIDTH = 480;
+        final int INPUT_HEIGHT = 640;
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {INPUT_WIDTH,INPUT_HEIGHT}).asFrameImage2D();
+
+        Bitmap bitmap = BitmapFactory.decodeStream(assetMgr.open("0002_000390.jpg"));
+        image.setBitmap(bitmap);
+
+        injectInputFrame("image", image);
+
+        process();
+        final float EXPECTED_RESULT = 0.29901487f;
+        assertEquals(EXPECTED_RESULT, ((Float) getOutputFrame("contrastRating").
+                asFrameValue().getValue()).floatValue(), 0.001f);
+
+
+    }
+}
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ExposureFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ExposureFilterTest.java
new file mode 100644
index 0000000..25ac212
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ExposureFilterTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+
+public class ExposureFilterTest extends MffFilterTestCase {
+
+    private AssetManager assetMgr = null;
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        assetMgr = mffContext.getApplicationContext().getAssets();
+        return new ExposureFilter(mffContext, "exposureFilter");
+    }
+
+    public void testExposureFilter() throws Exception{
+        final int INPUT_WIDTH = 480;
+        final int INPUT_HEIGHT = 640;
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {INPUT_WIDTH,INPUT_HEIGHT}).asFrameImage2D();
+
+        Bitmap bitmap = BitmapFactory.decodeStream(assetMgr.open("0002_000390.jpg"));
+        image.setBitmap(bitmap);
+
+        injectInputFrame("image", image);
+        process();
+        final float EXPECTED_OVEREXPOSURE = 0.00757f;
+        assertEquals(EXPECTED_OVEREXPOSURE, ((Float) getOutputFrame("overExposureRating").
+                asFrameValue().getValue()).floatValue(), 0.001f);
+        final float EXPECTED_UNDEREXPOSURE = 0.2077f;
+        assertEquals(EXPECTED_UNDEREXPOSURE, ((Float) getOutputFrame("underExposureRating").
+                asFrameValue().getValue()).floatValue(), 0.001f);
+    }
+}
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilterTest.java
new file mode 100644
index 0000000..02387fe
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FaceSquareFilterTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValues;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import android.hardware.Camera;
+import android.hardware.Camera.Face;
+import android.graphics.Rect;
+
+
+public class FaceSquareFilterTest extends MffFilterTestCase {
+
+    private AssetManager assetMgr = null;
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        assetMgr = mffContext.getApplicationContext().getAssets();
+        return new FaceSquareFilter(mffContext, "faceSquareFilter");
+    }
+
+    public void testFaceSquareFilter() throws Exception{
+        final int INPUT_WIDTH = 1536;
+        final int INPUT_HEIGHT = 2048;
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {INPUT_WIDTH,INPUT_HEIGHT}).asFrameImage2D();
+
+        FrameValues facesFrame = createFrame(FrameType.array(Camera.Face.class), new int[] {1,1}).
+                asFrameValues();
+
+        Bitmap bitmap = BitmapFactory.decodeStream(assetMgr.open("XZZ019.jpg"));
+        image.setBitmap(bitmap);
+        injectInputFrame("image", image);
+
+        Face face = new Face();
+        Rect faceRect = new Rect();
+        // These are the values for image 141 with 1 face
+        faceRect.set(-533, -453, 369, 224);
+        face.rect = faceRect;
+        Face[] faces = new Face[1];
+        faces[0] = face;
+        facesFrame.setValue(faces);
+        injectInputFrame("faces", facesFrame);
+        process();
+
+        // ensure the output image has the rectangle in the right place
+        FrameImage2D outputImage = getOutputFrame("image").asFrameImage2D();
+        int[] pixels = new int[bitmap.getByteCount()];
+        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
+                bitmap.getHeight());
+
+        final int FACE_X_RANGE = 2000;
+        final int WIDTH_OFFSET = 1000;
+        final int HEIGHT_OFFSET = 1000;
+
+        int top = (faceRect.top+HEIGHT_OFFSET)*bitmap.getHeight()/FACE_X_RANGE;
+        int bottom = (faceRect.bottom+HEIGHT_OFFSET)*bitmap.getHeight()/FACE_X_RANGE;
+        int left = (faceRect.left+WIDTH_OFFSET)*bitmap.getWidth()/FACE_X_RANGE;
+        int right = (faceRect.right+WIDTH_OFFSET)*bitmap.getWidth()/FACE_X_RANGE;
+
+        if (top < 0) {
+            top = 0;
+        } else if (top > bitmap.getHeight()) {
+            top = bitmap.getHeight();
+        }
+        if (left < 0) {
+            left = 0;
+        } else if (left > bitmap.getWidth()) {
+            left = bitmap.getWidth();
+        }
+        if (bottom > bitmap.getHeight()) {
+            bottom = bitmap.getHeight();
+        } else if (bottom < 0) {
+            bottom = 0;
+        }
+        if (right > bitmap.getWidth()) {
+            right = bitmap.getWidth();
+        } else if (right < 0) {
+            right = 0;
+        }
+
+        for (int j = 0; j < (bottom - top); j++) {
+            // Left edge
+            if (left > 0 && top > 0) {
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + left) +
+                       ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + left) +
+                       ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + left) +
+                       ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+            }
+
+            // Right edge
+            if (right > 0 && top > 0) {
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + right) +
+                       ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + right) +
+                       ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * (top + j) + right) +
+                       ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+            }
+
+        }
+        for (int k = 0; k < (right - left); k++) {
+            // Top edge
+            if (top < bitmap.getHeight()) {
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * top + left + k) +
+                       ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * top + left + k) +
+                       ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * top + left + k) +
+                       ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+
+            }
+            // Bottom edge
+            if (bottom < bitmap.getHeight()) {
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * bottom + left + k) +
+                       ImageConstants.RED_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * bottom + left + k) +
+                       ImageConstants.GREEN_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+                pixels[ImageConstants.PIX_CHANNELS * (bitmap.getWidth() * bottom + left + k) +
+                       ImageConstants.BLUE_OFFSET] = (byte) ImageConstants.MAX_BYTE;
+            }
+        }
+
+        Bitmap outputBitmap = outputImage.toBitmap();
+        int[] outputPixels = new int[outputBitmap.getByteCount()];
+        outputBitmap.getPixels(outputPixels, 0, outputBitmap.getWidth(), 0, 0,
+                outputBitmap.getWidth(), outputBitmap.getHeight());
+        int equalCount = 0;
+        for ( int i = 0; i < outputBitmap.getByteCount(); i++) {
+            if (pixels[i] == outputPixels[i])
+                equalCount++;
+        }
+
+        if (equalCount + (0.05f*outputBitmap.getByteCount()) < outputBitmap.getByteCount()) {
+            // Assertion will fail if condition is true
+            assertEquals(equalCount, outputBitmap.getByteCount());
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilterTest.java
new file mode 100644
index 0000000..0f4c6d0
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToSizeFilterTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.samples.simplecamera.FloatArrayToSizeFilter;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+
+public class FloatArrayToSizeFilterTest extends MffFilterTestCase {
+
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        return new FloatArrayToSizeFilter(mffContext, "floatArrayToSizeFilter");
+    }
+
+
+    public void testToSize() throws Exception {
+        FrameValue floatArray = createFrame(FrameType.array(float.class), new int[] { 1 }).
+                asFrameValue();
+        float[] floatArr = { 10f, 15f, 25f };
+        floatArray.setValue(floatArr);
+
+        injectInputFrame("array", floatArray);
+
+        process();
+        assertEquals(3, ((Integer) getOutputFrame("size").asFrameValue().getValue()).intValue());
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilterTest.java
new file mode 100644
index 0000000..bf6a197
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/FloatArrayToStrFilterTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+import androidx.media.filterfw.samples.simplecamera.FloatArrayToStrFilter;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+
+public class FloatArrayToStrFilterTest extends MffFilterTestCase {
+
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        return new FloatArrayToStrFilter(mffContext, "floatArrayToStrFilter");
+    }
+
+    public void testToStr() throws Exception {
+        FrameValue floatArray = createFrame(FrameType.array(float.class), new int[] { 1 }).
+                asFrameValue();
+        float[] floatArr = { 10f, 15f, 25f };
+        floatArray.setValue(floatArr);
+
+        injectInputFrame("array", floatArray);
+
+        process();
+
+        assertEquals("[10.0, 15.0, 25.0]", (String) getOutputFrame("string").asFrameValue().
+                getValue());
+    }
+}
\ No newline at end of file
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/IfElseFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/IfElseFilterTest.java
new file mode 100644
index 0000000..30835ea
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/IfElseFilterTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameImage2D;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+public class IfElseFilterTest extends MffFilterTestCase {
+    private final static int BIG_INPUT_WIDTH = 1536;
+    private final static int BIG_INPUT_HEIGHT = 2048;
+    private final static int SMALL_INPUT_WIDTH = 480;
+    private final static int SMALL_INPUT_HEIGHT = 640;
+
+    private AssetManager assetMgr = null;
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        assetMgr = mffContext.getApplicationContext().getAssets();
+        return new IfElseFilter(mffContext, "ifElseFilter");
+    }
+
+    public void testIfElseFilterTrue() throws Exception {
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {BIG_INPUT_WIDTH,BIG_INPUT_HEIGHT}).asFrameImage2D();
+        FrameImage2D video =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {SMALL_INPUT_WIDTH,SMALL_INPUT_HEIGHT}).asFrameImage2D();
+
+        // Image of legs
+        Bitmap videoBitmap = BitmapFactory.decodeStream(assetMgr.open("0002_000390.jpg"));
+        // Image of a face
+        Bitmap imageBitmap = BitmapFactory.decodeStream(assetMgr.open("XZZ019.jpg"));
+
+        image.setBitmap(imageBitmap);
+        injectInputFrame("falseResult", image);
+        video.setBitmap(videoBitmap);
+        injectInputFrame("trueResult", video);
+
+        FrameValue conditionFrame = createFrame(FrameType.single(boolean.class), new int[] {1}).
+                asFrameValue();
+        conditionFrame.setValue(true);
+        injectInputFrame("condition", conditionFrame);
+
+        process();
+
+        // Ensure that for true, we use the video input
+        FrameImage2D outputImage = getOutputFrame("output").asFrameImage2D();
+        assertEquals(outputImage, video);
+    }
+
+    public void testIfElseFilterFalse() throws Exception {
+
+        FrameImage2D image =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {BIG_INPUT_WIDTH,BIG_INPUT_HEIGHT}).asFrameImage2D();
+        FrameImage2D video =
+                createFrame(FrameType.image2D(FrameType.ELEMENT_RGBA8888, FrameType.READ_CPU),
+                        new int[] {SMALL_INPUT_WIDTH,SMALL_INPUT_HEIGHT}).asFrameImage2D();
+
+        // Image of legs
+        Bitmap videoBitmap = BitmapFactory.decodeStream(assetMgr.open("0002_000390.jpg"));
+        // Image of a face
+        Bitmap imageBitmap = BitmapFactory.decodeStream(assetMgr.open("XZZ019.jpg"));
+
+        image.setBitmap(imageBitmap);
+        injectInputFrame("falseResult", image);
+        video.setBitmap(videoBitmap);
+        injectInputFrame("trueResult", video);
+
+
+        FrameValue conditionFrame = createFrame(FrameType.single(boolean.class), new int[] {1}).
+                asFrameValue();
+        conditionFrame.setValue(false);
+        injectInputFrame("condition", conditionFrame);
+
+        process();
+
+        // Ensure that for true, we use the video input
+        FrameImage2D outputImage = getOutputFrame("output").asFrameImage2D();
+        assertEquals(outputImage, image);
+    }
+}
diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilterTest.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilterTest.java
new file mode 100644
index 0000000..43bc090
--- /dev/null
+++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/tests/src/androidx/media/filterfw/samples/simplecamera/ImageGoodnessFilterTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.media.filterfw.samples.simplecamera;
+
+import androidx.media.filterfw.Filter;
+import androidx.media.filterfw.FrameType;
+import androidx.media.filterfw.FrameValue;
+import androidx.media.filterfw.MffContext;
+import androidx.media.filterfw.MffFilterTestCase;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+public class ImageGoodnessFilterTest extends MffFilterTestCase {
+
+    String AWFUL_STRING = "Awful Picture";
+    String BAD_STRING  = "Bad Picture";
+    String OK_STRING = "Ok Picture";
+    String GOOD_STRING = "Good Picture!";
+    String GREAT_STRING = "Great Picture!";
+    @Override
+    protected Filter createFilter(MffContext mffContext) {
+        return new ImageGoodnessFilter(mffContext, "goodnessFilter");
+    }
+
+    public void testAwfulPicture() throws Exception {
+        FrameValue sharpnessFrame = createFrame(FrameType.single(), new int[] { 1 }).
+                asFrameValue();
+        sharpnessFrame.setValue(10f);
+        FrameValue oEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        oEFrame.setValue(0.39f);
+        FrameValue uEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        uEFrame.setValue(0.25f);
+        FrameValue colorFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        colorFrame.setValue(2.1f);
+        FrameValue contrastFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        contrastFrame.setValue(0.18f);
+        FrameValue motionFrame = createFrame(FrameType.array(), new int[] { 1 }).asFrameValue();
+        float[] motionFloatArray = { 9.0f, 3.0f, 2.0f };
+        motionFrame.setValue(motionFloatArray);
+
+        injectInputFrame("sharpness", sharpnessFrame);
+        injectInputFrame("overExposure", oEFrame);
+        injectInputFrame("underExposure", uEFrame);
+        injectInputFrame("colorfulness", colorFrame);
+        injectInputFrame("contrastRating", contrastFrame);
+        injectInputFrame("motionValues", motionFrame);
+
+        process();
+        assertEquals("Awful Picture", (String) getOutputFrame("goodOrBadPic").asFrameValue().
+                getValue());
+    }
+
+    public void testBadPicture() throws Exception {
+        FrameValue sharpnessFrame = createFrame(FrameType.single(), new int[] { 1 }).
+                asFrameValue();
+        sharpnessFrame.setValue(10f);
+        FrameValue oEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        oEFrame.setValue(0.39f);
+        FrameValue uEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        uEFrame.setValue(0.25f);
+        FrameValue colorFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        colorFrame.setValue(2.1f);
+        FrameValue contrastFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        contrastFrame.setValue(0.18f);
+        FrameValue motionFrame = createFrame(FrameType.array(), new int[] { 1 }).asFrameValue();
+        float[] motionFloatArray = { 0.0f, 0.0f, 0.0f };
+        motionFrame.setValue(motionFloatArray);
+
+        injectInputFrame("sharpness", sharpnessFrame);
+        injectInputFrame("overExposure", oEFrame);
+        injectInputFrame("underExposure", uEFrame);
+        injectInputFrame("colorfulness", colorFrame);
+        injectInputFrame("contrastRating", contrastFrame);
+        injectInputFrame("motionValues", motionFrame);
+
+        process();
+        assertEquals("Bad Picture", (String) getOutputFrame("goodOrBadPic").asFrameValue().
+                getValue());
+    }
+
+    public void testOkPicture() throws Exception {
+        FrameValue sharpnessFrame = createFrame(FrameType.single(), new int[] { 1 }).
+                asFrameValue();
+        sharpnessFrame.setValue(30f);
+        FrameValue oEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        oEFrame.setValue(0.39f);
+        FrameValue uEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        uEFrame.setValue(0.25f);
+        FrameValue colorFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        colorFrame.setValue(2.1f);
+        FrameValue contrastFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        contrastFrame.setValue(0.18f);
+        FrameValue motionFrame = createFrame(FrameType.array(), new int[] { 1 }).asFrameValue();
+        float[] motionFloatArray = { 0.0f, 0.0f, 0.0f };
+        motionFrame.setValue(motionFloatArray);
+
+        injectInputFrame("sharpness", sharpnessFrame);
+        injectInputFrame("overExposure", oEFrame);
+        injectInputFrame("underExposure", uEFrame);
+        injectInputFrame("colorfulness", colorFrame);
+        injectInputFrame("contrastRating", contrastFrame);
+        injectInputFrame("motionValues", motionFrame);
+
+        process();
+        assertEquals("Ok Picture", (String) getOutputFrame("goodOrBadPic").asFrameValue().
+                getValue());
+    }
+
+    public void testGoodPicture() throws Exception {
+        FrameValue sharpnessFrame = createFrame(FrameType.single(), new int[] { 1 }).
+                asFrameValue();
+        sharpnessFrame.setValue(50f);
+        FrameValue oEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        oEFrame.setValue(0.01f);
+        FrameValue uEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        uEFrame.setValue(0.01f);
+        FrameValue colorFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        colorFrame.setValue(2.1f);
+        FrameValue contrastFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        contrastFrame.setValue(0.18f);
+        FrameValue motionFrame = createFrame(FrameType.array(), new int[] { 1 }).asFrameValue();
+        float[] motionFloatArray = { 0.0f, 0.0f, 0.0f };
+        motionFrame.setValue(motionFloatArray);
+
+        injectInputFrame("sharpness", sharpnessFrame);
+        injectInputFrame("overExposure", oEFrame);
+        injectInputFrame("underExposure", uEFrame);
+        injectInputFrame("colorfulness", colorFrame);
+        injectInputFrame("contrastRating", contrastFrame);
+        injectInputFrame("motionValues", motionFrame);
+
+        process();
+        assertEquals("Good Picture!", (String) getOutputFrame("goodOrBadPic").asFrameValue().
+                getValue());
+    }
+
+    public void testGreatPicture() throws Exception {
+        FrameValue sharpnessFrame = createFrame(FrameType.single(), new int[] { 1 }).
+                asFrameValue();
+        sharpnessFrame.setValue(50f);
+        FrameValue oEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        oEFrame.setValue(0.01f);
+        FrameValue uEFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        uEFrame.setValue(0.02f);
+        FrameValue colorFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        colorFrame.setValue(2.1f);
+        FrameValue contrastFrame = createFrame(FrameType.single(), new int[] { 1 }).asFrameValue();
+        contrastFrame.setValue(0.25f);
+        FrameValue motionFrame = createFrame(FrameType.array(), new int[] { 1 }).asFrameValue();
+        float[] motionFloatArray = { 0.0f, 0.0f, 0.0f };
+        motionFrame.setValue(motionFloatArray);
+
+        injectInputFrame("sharpness", sharpnessFrame);
+        injectInputFrame("overExposure", oEFrame);
+        injectInputFrame("underExposure", uEFrame);
+        injectInputFrame("colorfulness", colorFrame);
+        injectInputFrame("contrastRating", contrastFrame);
+        injectInputFrame("motionValues", motionFrame);
+
+        process();
+        assertEquals("Great Picture!", (String) getOutputFrame("goodOrBadPic").asFrameValue().
+                getValue());
+    }
+}
diff --git a/tests/CoreTests/android/core/HttpHeaderTest.java b/tests/CoreTests/android/core/HttpHeaderTest.java
index eedbc3f..cc7c34c 100644
--- a/tests/CoreTests/android/core/HttpHeaderTest.java
+++ b/tests/CoreTests/android/core/HttpHeaderTest.java
@@ -20,10 +20,6 @@
 
 import android.net.http.Headers;
 import android.util.Log;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-
-import java.lang.reflect.Method;
 
 public class HttpHeaderTest extends AndroidTestCase {
 
@@ -67,38 +63,4 @@
         assertEquals("max-age=15,private", h.getCacheControl());
     }
 
-    // Test that cache behaves correctly when receiving a compund
-    // cache-control statement containing no-cache and max-age argument.
-    //
-    // If a cache control header contains both a max-age arument and
-    // a no-cache argument the max-age argument should be ignored.
-    // The resource can be cached, but a validity check must be done on
-    // every request. Test case checks that the expiry time is 0 for
-    // this item, so item will be validated on subsequent requests.
-    public void testCacheControlMultipleArguments() throws Exception {
-        // get private method CacheManager.parseHeaders()
-        Method m = CacheManager.class.getDeclaredMethod("parseHeaders",
-                new Class[] {int.class, Headers.class, String.class});
-        m.setAccessible(true);
-
-        // create indata
-        Headers h = new Headers();
-        CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append(CACHE_CONTROL_COMPOUND);
-        h.parseHeader(buffer);
-
-        CacheResult c = (CacheResult)m.invoke(null, 200, h, "text/html");
-
-        // Check that expires is set to 0, to ensure that no-cache has overridden
-        // the max-age argument
-        assertEquals(0, c.getExpires());
-
-        // check reverse order
-        buffer.clear();
-        buffer.append(CACHE_CONTROL_COMPOUND2);
-        h.parseHeader(buffer);
-
-        c = (CacheResult)m.invoke(null, 200, h, "text/html");
-        assertEquals(0, c.getExpires());
-    }
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 5a1928c..bfcc208 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -111,8 +111,6 @@
 
     String getConfigFile();
 
-    void captivePortalCheckComplete();
-
     void enableTdls(String remoteIPAddress, boolean enable);
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 5d130c6..dea0c6c 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -62,7 +62,6 @@
     private String mBSSID;
     private WifiSsid mWifiSsid;
     private int mNetworkId;
-    private boolean mHiddenSSID;
     /** Received Signal Strength Indicator */
     private int mRssi;
 
@@ -86,7 +85,6 @@
         mSupplicantState = SupplicantState.UNINITIALIZED;
         mRssi = -9999;
         mLinkSpeed = -1;
-        mHiddenSSID = false;
     }
 
     /**
@@ -99,7 +97,6 @@
             mBSSID = source.mBSSID;
             mWifiSsid = source.mWifiSsid;
             mNetworkId = source.mNetworkId;
-            mHiddenSSID = source.mHiddenSSID;
             mRssi = source.mRssi;
             mLinkSpeed = source.mLinkSpeed;
             mIpAddress = source.mIpAddress;
@@ -110,8 +107,6 @@
 
     void setSSID(WifiSsid wifiSsid) {
         mWifiSsid = wifiSsid;
-        // network is considered not hidden by default
-        mHiddenSSID = false;
     }
 
     /**
@@ -244,12 +239,7 @@
      * SSID-specific probe request must be used for scans.
      */
     public boolean getHiddenSSID() {
-        return mHiddenSSID;
-    }
-
-    /** {@hide} */
-    public void setHiddenSSID(boolean hiddenSSID) {
-        mHiddenSSID = hiddenSSID;
+        return mWifiSsid.isHidden();
     }
 
    /**
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7fc8bef..1fa7e85 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2157,13 +2157,6 @@
         }
     }
 
-    /** @hide */
-    public void captivePortalCheckComplete() {
-        try {
-            mService.captivePortalCheckComplete();
-        } catch (RemoteException e) {}
-    }
-
     protected void finalize() throws Throwable {
         try {
             synchronized (sThreadRefLock) {
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index a35a34b..f8ba95d 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -16,9 +16,8 @@
 
 package android.net.wifi;
 
-import android.os.Parcelable;
 import android.os.Parcel;
-import android.util.Log;
+import android.os.Parcelable;
 
 import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
@@ -42,7 +41,7 @@
 public class WifiSsid implements Parcelable {
     private static final String TAG = "WifiSsid";
 
-    public ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
+    public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
 
     private static final int HEX_RADIX = 16;
     public static final String NONE = "<unknown ssid>";
@@ -58,7 +57,6 @@
 
     public static WifiSsid createFromHex(String hexStr) {
         WifiSsid a = new WifiSsid();
-        int length = 0;
         if (hexStr == null) return a;
 
         if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
@@ -191,8 +189,13 @@
     }
 
     /** @hide */
+    public boolean isHidden() {
+        return isArrayAllZeroes(octets.toByteArray());
+    }
+
+    /** @hide */
     public byte[] getOctets() {
-        return  octets.toByteArray();
+        return octets.toByteArray();
     }
 
     /** @hide */
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 349fe24..f877c48 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -56,13 +56,11 @@
 import android.net.wifi.p2p.WifiP2pManager;
 import android.net.wifi.p2p.WifiP2pService;
 import android.os.BatteryStats;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -70,7 +68,6 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
-import android.util.Log;
 import android.util.LruCache;
 import android.text.TextUtils;
 
@@ -86,7 +83,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
-import java.net.Inet6Address;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -303,8 +299,6 @@
     static final int CMD_DELAYED_STOP_DRIVER              = BASE + 18;
     /* A delayed message sent to start driver when it fail to come up */
     static final int CMD_DRIVER_START_TIMED_OUT           = BASE + 19;
-    /* Ready to switch to network as default */
-    static final int CMD_CAPTIVE_CHECK_COMPLETE           = BASE + 20;
 
     /* Start the soft access point */
     static final int CMD_START_AP                         = BASE + 21;
@@ -536,8 +530,6 @@
     private State mObtainingIpState = new ObtainingIpState();
     /* Waiting for link quality verification to be complete */
     private State mVerifyingLinkState = new VerifyingLinkState();
-    /* Waiting for captive portal check to be complete */
-    private State mCaptivePortalCheckState = new CaptivePortalCheckState();
     /* Connected with IP addr */
     private State mConnectedState = new ConnectedState();
     /* disconnect issued, waiting for network disconnect confirmation */
@@ -774,7 +766,6 @@
                         addState(mL2ConnectedState, mConnectModeState);
                             addState(mObtainingIpState, mL2ConnectedState);
                             addState(mVerifyingLinkState, mL2ConnectedState);
-                            addState(mCaptivePortalCheckState, mL2ConnectedState);
                             addState(mConnectedState, mL2ConnectedState);
                         addState(mDisconnectingState, mConnectModeState);
                         addState(mDisconnectedState, mConnectModeState);
@@ -1031,7 +1022,8 @@
             int dist, distSd;
             long tsf = 0;
             dist = distSd = ScanResult.UNSPECIFIED;
-            long now = SystemClock.elapsedRealtime();
+            final long now = SystemClock.elapsedRealtime();
+            final int bssidStrLen = BSSID_STR.length();
 
             while (true) {
                 while (n < splitData.length) {
@@ -1068,7 +1060,8 @@
                     } else if (splitData[n].equals(TRUNCATED)) {
                         batchedScanResult.truncated = true;
                     } else if (splitData[n].startsWith(BSSID_STR)) {
-                        bssid = splitData[n].substring(BSSID_STR.length());
+                        bssid = new String(splitData[n].getBytes(), bssidStrLen,
+                                splitData[n].length() - bssidStrLen);
                     } else if (splitData[n].startsWith(FREQ_STR)) {
                         try {
                             freq = Integer.parseInt(splitData[n].substring(FREQ_STR.length()));
@@ -1275,10 +1268,6 @@
         }
     }
 
-    public void captivePortalCheckComplete() {
-        sendMessage(CMD_CAPTIVE_CHECK_COMPLETE);
-    }
-
     /**
      * TODO: doc
      */
@@ -1864,10 +1853,12 @@
         synchronized(mScanResultCache) {
             mScanResults = new ArrayList<ScanResult>();
             String[] lines = scanResults.split("\n");
+            final int bssidStrLen = BSSID_STR.length();
+            final int flagLen = FLAGS_STR.length();
 
             for (String line : lines) {
                 if (line.startsWith(BSSID_STR)) {
-                    bssid = line.substring(BSSID_STR.length());
+                    bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);
                 } else if (line.startsWith(FREQ_STR)) {
                     try {
                         freq = Integer.parseInt(line.substring(FREQ_STR.length()));
@@ -1891,7 +1882,7 @@
                         tsf = 0;
                     }
                 } else if (line.startsWith(FLAGS_STR)) {
-                    flags = line.substring(FLAGS_STR.length());
+                    flags = new String(line.getBytes(), flagLen, line.length() - flagLen);
                 } else if (line.startsWith(SSID_STR)) {
                     wifiSsid = WifiSsid.createFromAsciiEncoded(
                             line.substring(SSID_STR.length()));
@@ -2455,7 +2446,6 @@
                 case CMD_STOP_DRIVER:
                 case CMD_DELAYED_STOP_DRIVER:
                 case CMD_DRIVER_START_TIMED_OUT:
-                case CMD_CAPTIVE_CHECK_COMPLETE:
                 case CMD_START_AP:
                 case CMD_START_AP_SUCCESS:
                 case CMD_START_AP_FAILURE:
@@ -2958,7 +2948,9 @@
             if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 mWifiConfigStore.disableAllNetworks();
-                setWifiState(WIFI_STATE_DISABLED);
+                if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                    setWifiState(WIFI_STATE_DISABLED);
+                }
                 transitionTo(mScanModeState);
             } else {
                 /* Driver stop may have disabled networks, enable right after start */
@@ -3533,10 +3525,7 @@
 
         @Override
         public void exit() {
-            // if we're leaving before this is done, cleanup
-            if (mDhcpActive) {
-                handlePostDhcpSetup();
-            }
+            handleNetworkDisconnect();
         }
 
         @Override
@@ -3728,29 +3717,17 @@
                     break;
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
                     log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
-                    transitionTo(mCaptivePortalCheckState);
-                    break;
-                default:
-                    if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
+                    // Send out a broadcast with the CAPTIVE_PORTAL_CHECK to preserve
+                    // existing behaviour. The captive portal check really happens after we
+                    // transition into DetailedState.CONNECTED.
+                    setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
+                    mWifiConfigStore.updateStatus(mLastNetworkId,
+                            DetailedState.CAPTIVE_PORTAL_CHECK);
+                    sendNetworkStateChangeBroadcast(mLastBssid);
 
-    class CaptivePortalCheckState extends State {
-        @Override
-        public void enter() {
-            log(getName() + " enter");
-            setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
-            mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
-            sendNetworkStateChangeBroadcast(mLastBssid);
-        }
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_CAPTIVE_CHECK_COMPLETE:
-                    log(getName() + " CMD_CAPTIVE_CHECK_COMPLETE");
+                    // NOTE: This might look like an odd place to enable IPV6 but this is in
+                    // response to transitioning into GOOD_LINK_DETECTED. Similarly, we disable
+                    // ipv6 when we transition into POOR_LINK_DETECTED in mConnectedState.
                     try {
                         mNwService.enableIpv6(mInterfaceName);
                     } catch (RemoteException re) {
@@ -3758,12 +3735,16 @@
                     } catch (IllegalStateException e) {
                         loge("Failed to enable IPv6: " + e);
                     }
+
+                    log(getName() + " GOOD_LINK_DETECTED: transition to CONNECTED");
                     setNetworkDetailedState(DetailedState.CONNECTED);
                     mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
                     sendNetworkStateChangeBroadcast(mLastBssid);
                     transitionTo(mConnectedState);
+
                     break;
                 default:
+                    if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -3795,6 +3776,7 @@
             }
             return HANDLED;
         }
+
         @Override
         public void exit() {
             /* Request a CS wakelock during transition to mobile */
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 615c893..7ded171 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -115,14 +115,6 @@
         return true;
     }
 
-    /**
-     * Captive check is complete, switch to network
-     */
-    @Override
-    public void captivePortalCheckComplete() {
-        mWifiManager.captivePortalCheckComplete();
-    }
-
     @Override
     public void captivePortalCheckCompleted(boolean isCaptivePortal) {
         // not implemented
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 625ffb8..23058f4 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -1080,6 +1080,7 @@
 
         @Override
         public void exit() {
+            sendP2pDiscoveryChangedBroadcast(false);
             sendP2pStateChangedBroadcast(false);
             mNetworkInfo.setIsAvailable(false);
         }