am d5abe7ea: am ccd11ec5: DO NOT MERGE Increase the system-image revision number after opengl bug fix

* commit 'd5abe7ea86d685009afa907c00a7bc60d4dbfe7d':
  DO NOT MERGE Increase the system-image revision number after opengl bug fix
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml
index f2bf60c..32738dd 100644
--- a/apps/Development/AndroidManifest.xml
+++ b/apps/Development/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
@@ -169,6 +170,13 @@
         </receiver>
         <service android:name="BadBehaviorActivity$BadService" />
 
+        <activity android:name="CacheAbuser" android:label="Cache Abuser">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="ConfigurationViewer" android:label="Configuration">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/apps/Development/res/layout/bad_behavior.xml b/apps/Development/res/layout/bad_behavior.xml
index ce10ebb..6415da6 100644
--- a/apps/Development/res/layout/bad_behavior.xml
+++ b/apps/Development/res/layout/bad_behavior.xml
@@ -21,7 +21,7 @@
     <LinearLayout
         android:orientation="vertical"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="wrap_content">
 
         <Button android:id="@+id/bad_behavior_crash_main"
                 android:layout_width="match_parent"
diff --git a/apps/Development/res/layout/cache_abuser.xml b/apps/Development/res/layout/cache_abuser.xml
new file mode 100644
index 0000000..8e212a9
--- /dev/null
+++ b/apps/Development/res/layout/cache_abuser.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <Button android:id="@+id/start_internal_abuse"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/cache_abuser_start_internal_abuse" />
+
+        <Button android:id="@+id/start_slow_internal_abuse"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/cache_abuser_start_slow_internal_abuse" />
+
+        <Button android:id="@+id/start_external_abuse"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/cache_abuser_start_external_abuse" />
+
+        <Button android:id="@+id/start_slow_external_abuse"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/cache_abuser_start_slow_external_abuse" />
+
+        <Button android:id="@+id/stop_abuse"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/cache_abuser_stop_abuse" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml
index 0f4763c..1087931 100644
--- a/apps/Development/res/values/strings.xml
+++ b/apps/Development/res/values/strings.xml
@@ -220,6 +220,13 @@
     <string name="bad_behavior_anr_system_label">System ANR (in ActivityManager)</string>
     <string name="bad_behavior_wedge_system_label">Wedge system (5 minute system ANR)</string>
 
+    <!-- CacheAbuser -->
+    <string name="cache_abuser_start_internal_abuse">Quickly abuse internal cache</string>
+    <string name="cache_abuser_start_slow_internal_abuse">Slowly abuse internal cache</string>
+    <string name="cache_abuser_start_external_abuse">Quickly abuse external cache</string>
+    <string name="cache_abuser_start_slow_external_abuse">Slowly abuse external cache</string>
+    <string name="cache_abuser_stop_abuse">Stop cache abuse</string>
+
     <!-- MediaScannerActivity -->
     <string name="scancard">Scan SD card</string>
     <string name="numsongs"># of albums</string>
diff --git a/apps/Development/src/com/android/development/CacheAbuser.java b/apps/Development/src/com/android/development/CacheAbuser.java
new file mode 100644
index 0000000..489018f
--- /dev/null
+++ b/apps/Development/src/com/android/development/CacheAbuser.java
@@ -0,0 +1,190 @@
+/*
+ * 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 com.android.development;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.IActivityController;
+import android.app.IActivityManager;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+
+public class CacheAbuser extends Activity {
+    Button mStartInternalAbuse;
+    Button mStartSlowInternalAbuse;
+    Button mStartExternalAbuse;
+    Button mStartSlowExternalAbuse;
+    Button mStopAbuse;
+
+    AsyncTask<Void, Void, Void> mInternalAbuseTask;
+    AsyncTask<Void, Void, Void> mExternalAbuseTask;
+
+    static class AbuseTask extends AsyncTask<Void, Void, Void> {
+        final File mBaseDir;
+        final boolean mQuick;
+        final byte[] mBuffer;
+
+        AbuseTask(File cacheDir, boolean quick) {
+            File dir = new File(cacheDir, quick ? "quick" : "slow");
+            mBaseDir = new File(dir, Long.toString(System.currentTimeMillis()));
+            mQuick = quick;
+            mBuffer = quick ? new byte[1024*1024] : new byte[1024];
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            long num = 0;
+            while (!isCancelled()) {
+                long dir1num = num/100;
+                long dir2num = num%100;
+                File dir = new File(mBaseDir, Long.toString(dir1num));
+                File file = new File(dir, Long.toString(dir2num));
+                FileOutputStream fos = null;
+                try {
+                    dir.mkdirs();
+                    fos = new FileOutputStream(file, false);
+                    fos.write(mBuffer);
+                } catch (IOException e) {
+                    Log.w("CacheAbuser", "Write failed to " + file + ": " + e);
+                    try {
+                        wait(5*1000);
+                    } catch (InterruptedException e1) {
+                    }
+                } finally {
+                    try {
+                        if (fos != null) {
+                            fos.close();
+                        }
+                    } catch (IOException e) {
+                    }
+                }
+                num++;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.cache_abuser);
+
+        mStartInternalAbuse = (Button) findViewById(R.id.start_internal_abuse);
+        mStartInternalAbuse.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mInternalAbuseTask == null) {
+                    mInternalAbuseTask = new AbuseTask(getCacheDir(), true);
+                    mInternalAbuseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                    updateButtonState();
+                }
+            }
+        });
+
+        mStartSlowInternalAbuse = (Button) findViewById(R.id.start_slow_internal_abuse);
+        mStartSlowInternalAbuse.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mInternalAbuseTask == null) {
+                    mInternalAbuseTask = new AbuseTask(getCacheDir(), false);
+                    mInternalAbuseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                    updateButtonState();
+                }
+            }
+        });
+
+        mStartExternalAbuse = (Button) findViewById(R.id.start_external_abuse);
+        mStartExternalAbuse.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mExternalAbuseTask == null) {
+                    mExternalAbuseTask = new AbuseTask(getExternalCacheDir(), true);
+                    mExternalAbuseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                    updateButtonState();
+                }
+            }
+        });
+
+        mStartSlowExternalAbuse = (Button) findViewById(R.id.start_slow_external_abuse);
+        mStartSlowExternalAbuse.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mExternalAbuseTask == null) {
+                    mExternalAbuseTask = new AbuseTask(getExternalCacheDir(), false);
+                    mExternalAbuseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                    updateButtonState();
+                }
+            }
+        });
+
+        mStopAbuse = (Button) findViewById(R.id.stop_abuse);
+        mStopAbuse.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                stopAbuse();
+            }
+        });
+
+        updateButtonState();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        updateButtonState();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        stopAbuse();
+    }
+
+    void stopAbuse() {
+        if (mInternalAbuseTask != null) {
+            mInternalAbuseTask.cancel(false);
+            mInternalAbuseTask = null;
+        }
+        if (mExternalAbuseTask != null) {
+            mExternalAbuseTask.cancel(false);
+            mExternalAbuseTask = null;
+        }
+        updateButtonState();
+    }
+
+    void updateButtonState() {
+        mStartInternalAbuse.setEnabled(mInternalAbuseTask == null);
+        mStartSlowInternalAbuse.setEnabled(mInternalAbuseTask == null);
+        mStartExternalAbuse.setEnabled(mExternalAbuseTask == null);
+        mStartSlowExternalAbuse.setEnabled(mExternalAbuseTask == null);
+        mStopAbuse.setEnabled(mInternalAbuseTask != null
+                || mExternalAbuseTask != null);
+    }
+}
diff --git a/apps/Development/src/com/android/development/Development.java b/apps/Development/src/com/android/development/Development.java
index a3979f2..8a317cd 100644
--- a/apps/Development/src/com/android/development/Development.java
+++ b/apps/Development/src/com/android/development/Development.java
@@ -16,8 +16,13 @@
 
 package com.android.development;
 
+import java.util.List;
+
 import android.app.LauncherActivity;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
 
 public class Development extends LauncherActivity
 {
@@ -25,7 +30,17 @@
     protected Intent getTargetIntent() {
         Intent targetIntent = new Intent(Intent.ACTION_MAIN, null);
         targetIntent.addCategory(Intent.CATEGORY_TEST);
-        targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return targetIntent;
     }
+
+    protected void onSortResultList(List<ResolveInfo> results) {
+        super.onSortResultList(results);
+        Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
+        List<ResolveInfo> topItems = getPackageManager().queryIntentActivities(
+                settingsIntent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (topItems != null) {
+            super.onSortResultList(topItems);
+            results.addAll(0, topItems);
+        }
+    }
 }
diff --git a/apps/Development/src/com/android/development/InstrumentationList.java b/apps/Development/src/com/android/development/InstrumentationList.java
index 88f09f8..eb6c251 100644
--- a/apps/Development/src/com/android/development/InstrumentationList.java
+++ b/apps/Development/src/com/android/development/InstrumentationList.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -154,7 +155,7 @@
             }
             try {
                 ActivityManagerNative.getDefault().
-                    startInstrumentation(className, profilingFile, 0, null, mWatcher);
+                    startInstrumentation(className, profilingFile, 0, null, mWatcher, UserHandle.myUserId());
             } catch (RemoteException ex) {
             }
         }
diff --git a/apps/Development/src/com/android/development/PackageSummary.java b/apps/Development/src/com/android/development/PackageSummary.java
index d621d4e..452f4d7 100644
--- a/apps/Development/src/com/android/development/PackageSummary.java
+++ b/apps/Development/src/com/android/development/PackageSummary.java
@@ -17,7 +17,7 @@
 package com.android.development;
 
 import android.app.Activity;
-import android.app.ActivityManagerNative;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,7 +30,6 @@
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -39,7 +38,6 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-
 public class PackageSummary extends Activity {
 
     String mPackageName;
@@ -151,10 +149,9 @@
 
             mRestart.setOnClickListener(new View.OnClickListener() {
                 public void onClick(View v) {
-                    try {
-                        ActivityManagerNative.getDefault().killBackgroundProcesses(mPackageName);
-                    } catch (RemoteException e) {
-                    }
+                    ActivityManager am = (ActivityManager)getSystemService(
+                            Context.ACTIVITY_SERVICE);
+                    am.killBackgroundProcesses(mPackageName);
                 }
             });
             
diff --git a/apps/Development/src/com/android/development/SyncAdapterDriver.java b/apps/Development/src/com/android/development/SyncAdapterDriver.java
index 3a21903..1bf27ae 100644
--- a/apps/Development/src/com/android/development/SyncAdapterDriver.java
+++ b/apps/Development/src/com/android/development/SyncAdapterDriver.java
@@ -35,6 +35,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.widget.ArrayAdapter;
 import android.widget.AdapterView;
 import android.widget.Spinner;
@@ -88,14 +89,9 @@
         mSyncAdaptersCache.setListener(this, null /* Handler */);
     }
 
-    protected void onDestroy() {
-        mSyncAdaptersCache.close();
-        super.onDestroy();
-    }
-
     private void getSyncAdapters() {
         Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> all =
-                mSyncAdaptersCache.getAllServices();
+                mSyncAdaptersCache.getAllServices(UserHandle.myUserId());
         synchronized (mSyncAdaptersLock) {
             mSyncAdapters = new Object[all.size()];
             String[] names = new String[mSyncAdapters.length];
@@ -201,7 +197,7 @@
         updateUi();
     }
 
-    public void onServiceChanged(SyncAdapterType type, boolean removed) {
+    public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
         getSyncAdapters();
     }
 
diff --git a/apps/DevelopmentSettings/Android.mk b/apps/DevelopmentSettings/Android.mk
new file mode 100644
index 0000000..ae39e25
--- /dev/null
+++ b/apps/DevelopmentSettings/Android.mk
@@ -0,0 +1,6 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := DevelopmentSettings
+
+include $(BUILD_PACKAGE)
diff --git a/apps/DevelopmentSettings/AndroidManifest.xml b/apps/DevelopmentSettings/AndroidManifest.xml
new file mode 100644
index 0000000..3dd0a0b
--- /dev/null
+++ b/apps/DevelopmentSettings/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.development_settings"
+        android:versionCode="1" android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application android:hasCode="false" android:label="@string/app_label"
+            android:icon="@mipmap/ic_launcher_devsettings">
+        <activity android:name="android.app.AliasActivity"
+                android:label="@string/activity_label">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.app.alias"
+                    android:resource="@xml/alias" />
+        </activity>
+    </application>
+</manifest>
diff --git a/apps/DevelopmentSettings/NOTICE b/apps/DevelopmentSettings/NOTICE
new file mode 100644
index 0000000..c77f135
--- /dev/null
+++ b/apps/DevelopmentSettings/NOTICE
@@ -0,0 +1,190 @@
+
+   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.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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/apps/DevelopmentSettings/res/mipmap-hdpi/ic_launcher_devsettings.png b/apps/DevelopmentSettings/res/mipmap-hdpi/ic_launcher_devsettings.png
new file mode 100644
index 0000000..32da776
--- /dev/null
+++ b/apps/DevelopmentSettings/res/mipmap-hdpi/ic_launcher_devsettings.png
Binary files differ
diff --git a/apps/DevelopmentSettings/res/mipmap-mdpi/ic_launcher_devsettings.png b/apps/DevelopmentSettings/res/mipmap-mdpi/ic_launcher_devsettings.png
new file mode 100644
index 0000000..32a22ac
--- /dev/null
+++ b/apps/DevelopmentSettings/res/mipmap-mdpi/ic_launcher_devsettings.png
Binary files differ
diff --git a/apps/DevelopmentSettings/res/mipmap-xhdpi/ic_launcher_devsettings.png b/apps/DevelopmentSettings/res/mipmap-xhdpi/ic_launcher_devsettings.png
new file mode 100644
index 0000000..9bfdd78
--- /dev/null
+++ b/apps/DevelopmentSettings/res/mipmap-xhdpi/ic_launcher_devsettings.png
Binary files differ
diff --git a/apps/DevelopmentSettings/res/values/strings.xml b/apps/DevelopmentSettings/res/values/strings.xml
new file mode 100644
index 0000000..4803840
--- /dev/null
+++ b/apps/DevelopmentSettings/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <string name="app_label">Development Settings</string>
+    <string name="activity_label">Dev Settings</string>
+</resources>
diff --git a/apps/DevelopmentSettings/res/xml/alias.xml b/apps/DevelopmentSettings/res/xml/alias.xml
new file mode 100644
index 0000000..0786674
--- /dev/null
+++ b/apps/DevelopmentSettings/res/xml/alias.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<alias xmlns:android="http://schemas.android.com/apk/res/android">
+    <intent android:action="android.settings.APPLICATION_DEVELOPMENT_SETTINGS">
+    </intent>
+</alias>
diff --git a/apps/Fallback/res/values-fa/strings.xml b/apps/Fallback/res/values-fa/strings.xml
index 6f6c60f..7c0256b 100644
--- a/apps/Fallback/res/values-fa/strings.xml
+++ b/apps/Fallback/res/values-fa/strings.xml
@@ -18,5 +18,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="appTitle" msgid="161410001913116606">"بازگشت"</string>
     <string name="title" msgid="8156274565006125136">"عملکرد پشتیبانی نشده"</string>
-    <string name="error" msgid="6539615832923362301">"آن عملکرد در حال حاضر پشتیبانی نمی شود."</string>
+    <string name="error" msgid="6539615832923362301">"آن عملکرد در حال حاضر پشتیبانی نمی‌شود."</string>
 </resources>
diff --git a/apps/SdkSetup/src/com/android/sdksetup/DefaultActivity.java b/apps/SdkSetup/src/com/android/sdksetup/DefaultActivity.java
index e87533d..3a0b2a3 100644
--- a/apps/SdkSetup/src/com/android/sdksetup/DefaultActivity.java
+++ b/apps/SdkSetup/src/com/android/sdksetup/DefaultActivity.java
@@ -38,14 +38,15 @@
         super.onCreate(icicle);
         
         // Add a persistent setting to allow other apps to know the device has been provisioned.
-        Settings.Secure.putInt(getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 1);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
 
         // Enable the GPS.
         // Not needed since this SDK will contain the Settings app.
-        Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, LocationManager.GPS_PROVIDER);
+        Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                LocationManager.GPS_PROVIDER);
         
         // enable install from non market
-        Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 1);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.INSTALL_NON_MARKET_APPS, 1);
 
         // provision the backup manager.
         IBackupManager bm = IBackupManager.Stub.asInterface(
diff --git a/build/sdk.atree b/build/sdk.atree
index e0c25ba..fe9c3cf 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -193,6 +193,7 @@
 development/samples/TicTacToeMain              samples/${PLATFORM_NAME}/TicTacToeMain
 development/samples/TtsEngine                  samples/${PLATFORM_NAME}/TtsEngine
 development/samples/ToyVpn                     samples/${PLATFORM_NAME}/ToyVpn
+development/samples/UiAutomator                samples/${PLATFORM_NAME}/UiAutomator
 development/samples/USB/MissileLauncher        samples/${PLATFORM_NAME}/USB/MissileLauncher
 development/samples/USB/AdbTest                samples/${PLATFORM_NAME}/USB/AdbTest
 development/samples/VoiceRecognitionService    samples/${PLATFORM_NAME}/VoiceRecognitionService
diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
index fcf0893..5e86a79 100644
--- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java
+++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
@@ -32,7 +32,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.UserId;
+import android.os.UserHandle;
 import android.view.IWindowManager;
 import android.view.Surface;
 
@@ -411,7 +411,7 @@
             if (mRequestBugreport) {
                 logOutput =
                         new BufferedWriter(new FileWriter(new File(Environment
-                                .getExternalStorageDirectory(), reportName), true));
+                                .getLegacyExternalStorageDirectory(), reportName), true));
             }
             // pipe everything from process stdout -> System.err
             InputStream inStream = p.getInputStream();
@@ -444,7 +444,7 @@
         // TO DO: Add the script file name to the log.
         try {
             Writer output = new BufferedWriter(new FileWriter(new File(
-                    Environment.getExternalStorageDirectory(), "scriptlog.txt"), true));
+                    Environment.getLegacyExternalStorageDirectory(), "scriptlog.txt"), true));
             output.write("iteration: " + count + " time: "
                     + MonkeyUtils.toCalendarTime(System.currentTimeMillis()) + "\n");
             output.close();
@@ -516,6 +516,10 @@
             mMainCategories.add(Intent.CATEGORY_MONKEY);
         }
 
+        if (mSeed == 0) {
+            mSeed = System.currentTimeMillis() + System.identityHashCode(this);
+        }
+
         if (mVerbose > 0) {
             System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
             if (mValidPackages.size() > 0) {
@@ -550,8 +554,7 @@
             return -4;
         }
 
-        mRandom = new SecureRandom();
-        mRandom.setSeed((mSeed == 0) ? -1 : mSeed);
+        mRandom = new Random(mSeed);
 
         if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
             // script mode, ignore other options
@@ -953,7 +956,7 @@
                     intent.addCategory(category);
                 }
                 List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
-                        UserId.myUserId());
+                        UserHandle.myUserId());
                 if (mainApps == null || mainApps.size() == 0) {
                     System.err.println("// Warning: no activities found for category " + category);
                     continue;
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyInstrumentationEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyInstrumentationEvent.java
index 9ba3191..ec3160c 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeyInstrumentationEvent.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyInstrumentationEvent.java
@@ -46,7 +46,7 @@
         Bundle args = new Bundle();
         args.putString("class", mTestCaseName);
         try {
-            iam.startInstrumentation(cn, null, 0, args, null);
+            iam.startInstrumentation(cn, null, 0, args, null, 0);
         } catch (RemoteException e) {
             System.err.println("** Failed talking with activity manager!");
             return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyNetworkMonitor.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyNetworkMonitor.java
index 6af9940..191bb59 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeyNetworkMonitor.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyNetworkMonitor.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 
 /**
  * Class for monitoring network connectivity during monkey runs.
@@ -40,7 +41,7 @@
     private long mElapsedTime = 0; // amount of time spent between start() and stop()
     
     public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
-            boolean ordered, boolean sticky) throws RemoteException {
+            boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
         NetworkInfo ni = (NetworkInfo) intent.getParcelableExtra(
                 ConnectivityManager.EXTRA_NETWORK_INFO);
         if (LDEBUG) System.out.println("Network state changed: " 
@@ -84,7 +85,7 @@
 
     public void register(IActivityManager am) throws RemoteException {
         if (LDEBUG) System.out.println("registering Receiver");
-        am.registerReceiver(null, null, this, filter, null); 
+        am.registerReceiver(null, null, this, filter, null, UserHandle.USER_ALL); 
     }
     
     public void unregister(IActivityManager am) throws RemoteException {
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetwork.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetwork.java
index 99e7c07..cae6416 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetwork.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetwork.java
@@ -437,7 +437,7 @@
         IPowerManager pm =
                 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
         try {
-            pm.userActivityWithForce(SystemClock.uptimeMillis(), true, true);
+            pm.wakeUp(SystemClock.uptimeMillis());
         } catch (RemoteException e) {
             Log.e(TAG, "Got remote exception", e);
             return false;
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkVars.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkVars.java
index eea00c7..9abfe26 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkVars.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkVars.java
@@ -15,10 +15,10 @@
  */
 package com.android.commands.monkey;
 
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.SystemClock;
 import android.view.Display;
-import android.view.WindowManagerImpl;
 import android.util.DisplayMetrics;
 
 import com.android.commands.monkey.MonkeySourceNetwork.CommandQueue;
@@ -82,7 +82,7 @@
         VAR_MAP.put("build.version.codename", new StaticVarGetter(Build.VERSION.CODENAME));
 
         // Display
-        Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
         VAR_MAP.put("display.width", new StaticVarGetter(Integer.toString(display.getWidth())));
         VAR_MAP.put("display.height", new StaticVarGetter(Integer.toString(display.getHeight())));
 
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkViews.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkViews.java
index 590f406..ddb83da 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkViews.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceNetworkViews.java
@@ -24,7 +24,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserId;
+import android.os.UserHandle;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -140,7 +140,7 @@
         try {
             AccessibilityNodeInfo node = event.getSource();
             String packageName = node.getPackageName().toString();
-            ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserId.myUserId());
+            ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserHandle.myUserId());
             Class<?> klass;
             klass = getIdClass(packageName, appInfo.sourceDir);
             return klass.getField(stringId).getInt(null);
@@ -195,7 +195,7 @@
             String packageName = node.getPackageName().toString();
             try{
                 Class<?> klass;
-                ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserId.myUserId());
+                ApplicationInfo appInfo = sPm.getApplicationInfo(packageName, 0, UserHandle.myUserId());
                 klass = getIdClass(packageName, appInfo.sourceDir);
                 StringBuilder fieldBuilder = new StringBuilder();
                 Field[] fields = klass.getFields();
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
index 3d940a8..a7f538d 100644
--- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
@@ -18,13 +18,13 @@
 
 import android.content.ComponentName;
 import android.graphics.PointF;
+import android.hardware.display.DisplayManagerGlobal;
 import android.os.SystemClock;
 import android.view.Display;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
-import android.view.WindowManagerImpl;
 
 import java.util.ArrayList;
 import java.util.Random;
@@ -258,8 +258,8 @@
      * @param gesture The gesture to perform.
      *
      */
-    private void generatePointerEvent(Random random, int gesture){
-        Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+    private void generatePointerEvent(Random random, int gesture) {
+        Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
 
         PointF p1 = randomPoint(random, display);
         PointF v1 = randomVector(random);
@@ -350,10 +350,6 @@
      *
      */
     private void generateTrackballEvent(Random random) {
-        Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
-
-        boolean drop = false;
-        int count = random.nextInt(10);
         for (int i = 0; i < 10; ++i) {
             // generate a small random step
             int dX = random.nextInt(10) - 5;
diff --git a/data/etc/apns-conf.xml b/data/etc/apns-conf.xml
deleted file mode 100644
index 2fe90d9..0000000
--- a/data/etc/apns-conf.xml
+++ /dev/null
@@ -1,265 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2006, Google Inc.
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- use empty string to specify no proxy or port -->
-<!-- This version must agree with that in apps/common/res/apns.xml -->
-<apns version="7">
-    <apn carrier="T-Mobile US"
-         mcc="310"
-         mnc="260"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-    />
-
-    <apn carrier="T-Mobile US 250"
-         mcc="310"
-         mnc="250"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 660"
-         mcc="310"
-         mnc="660"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 230"
-         mcc="310"
-         mnc="230"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 310"
-         mcc="310"
-         mnc="310"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 580"
-         mcc="310"
-         mnc="580"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 240"
-         mcc="310"
-         mnc="240"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 800"
-         mcc="310"
-         mnc="800"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 210"
-         mcc="310"
-         mnc="210"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 160"
-         mcc="310"
-         mnc="160"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 270"
-         mcc="310"
-         mnc="270"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 200"
-         mcc="310"
-         mnc="200"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 220"
-         mcc="310"
-         mnc="220"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <apn carrier="T-Mobile US 490"
-         mcc="310"
-         mnc="490"
-         apn="epc.tmobile.com"
-         user="none"
-         server="*"
-         password="none"
-         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
-            />
-
-    <!-- T-Mobile Europe -->
-    <apn carrier="T-Mobile UK"
-         mcc="234"
-         mnc="30"
-         apn="general.t-mobile.uk"
-         user="t-mobile"
-         password="tm"
-         server="*"
-         mmsproxy="149.254.201.135"
-         mmsport="8080"
-         mmsc="http://mmsc.t-mobile.co.uk:8002"
-    />
-
-    <apn carrier="T-Mobile D"
-         mcc="262"
-         mnc="01"
-         apn="internet.t-mobile"
-         user="t-mobile"
-         password="tm"
-         server="*"
-         mmsproxy="172.028.023.131"
-         mmsport="8008"
-         mmsc="http://mms.t-mobile.de/servlets/mms"
-    />
-
-    <apn carrier="T-Mobile A"
-         mcc="232"
-         mnc="03"
-         apn="gprsinternet"
-         user="t-mobile"
-         password="tm"
-         server="*"
-         mmsproxy="010.012.000.020"
-         mmsport="80"
-         mmsc="http://mmsc.t-mobile.at/servlets/mms"
-         type="default,supl"
-    />
-
-    <apn carrier="T-Mobile A MMS"
-         mcc="232"
-         mnc="03"
-         apn="gprsmms"
-         user="t-mobile"
-         password="tm"
-         server="*"
-         mmsproxy="010.012.000.020"
-         mmsport="80"
-         mmsc="http://mmsc.t-mobile.at/servlets/mms"
-         type="mms"
-    />
-
-    <apn carrier="T-Mobile CZ"
-         mcc="230"
-         mnc="01"
-         apn="internet.t-mobile.cz"
-         user="wap"
-         password="wap"
-         server="*"
-         mmsproxy="010.000.000.010"
-         mmsport="80"
-         mmsc="http://mms"
-         type="default,supl"
-    />
-
-    <apn carrier="T-Mobile CZ MMS"
-         mcc="230"
-         mnc="01"
-         apn="mms.t-mobile.cz"
-         user="mms"
-         password="mms"
-         server="*"
-         mmsproxy="010.000.000.010"
-         mmsport="80"
-         mmsc="http://mms"
-         type="mms"
-    />
-
-    <apn carrier="T-Mobile NL"
-         mcc="204"
-         mnc="16"
-         apn="internet"
-         user="*"
-         password="*"
-         server="*"
-         mmsproxy="010.010.010.011"
-         mmsport="8080"
-         mmsc="http://t-mobilemms"
-         type="default,supl"
-    />
-
-    <apn carrier="T-Mobile NL MMS"
-         mcc="204"
-         mnc="16"
-         apn="mms"
-         user="tmobilemms"
-         password="tmobilemms"
-         server="*"
-         mmsproxy="010.010.010.011"
-         mmsport="8080"
-         mmsc="http://t-mobilemms"
-         type="mms"
-    />
-</apns>
diff --git a/data/etc/apns-conf_sdk.xml b/data/etc/apns-conf_sdk.xml
deleted file mode 100644
index 0e9cf7d..0000000
--- a/data/etc/apns-conf_sdk.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This file contains fake APNs that are necessary for the emulator
-     to talk to the network.  It should only be installed for SDK builds.
-
-     This file is not installed by the local Android.mk, it's installed using
-     a PRODUCT_COPY_FILES line in the sdk section of the toplevel Makefile.
--->
-
-<!-- use empty string to specify no proxy or port -->
-<!-- This version must agree with that in apps/common/res/apns.xml -->
-<apns version="7">
-    <apn carrier="Android"
-        mcc="310"
-        mnc="995"
-        apn="internet"
-        user="*"
-        server="*"
-        password="*"
-        mmsc="null"
-    />
-    <apn carrier="TelKila"
-        mcc="310"
-        mnc="260"
-        apn="internet"
-        user="*"
-        server="*"
-        password="*"
-        mmsc="null"
-    />
-</apns>
diff --git a/data/etc/vold.conf b/data/etc/vold.conf
deleted file mode 100644
index 7888936..0000000
--- a/data/etc/vold.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-## vold configuration file for the emulator/SDK
-
-volume_sdcard {
-    ## This is the direct uevent device path to the SD slot on the device
-    emu_media_path /devices/platform/goldfish_mmc.0/mmc_host/mmc0
-
-    media_type     mmc
-    mount_point    /sdcard
-    ums_path       /devices/platform/usb_mass_storage/lun0
-}
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index fac25bb..6a43179 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -1,22 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="packages/apps/Bluetooth/src"/>
+	<classpathentry kind="src" path="packages/apps/Camera/src"/>
 	<classpathentry kind="src" path="packages/apps/Browser/src"/>
 	<classpathentry kind="src" path="packages/apps/Calendar/src"/>
+	<classpathentry kind="src" path="packages/apps/Calendar/extensions_src"/>
 	<classpathentry kind="src" path="packages/apps/Calculator/src"/>
-	<classpathentry kind="src" path="packages/apps/Camera/src"/>
 	<classpathentry kind="src" path="packages/apps/CertInstaller/src"/>
 	<classpathentry kind="src" path="packages/apps/Contacts/src"/>
 	<classpathentry kind="src" path="packages/apps/DeskClock/src"/>
 	<classpathentry kind="src" path="packages/apps/Email/src"/>
 	<classpathentry kind="src" path="packages/apps/Email/emailcommon/src"/>
-	<classpathentry kind="src" path="packages/apps/Exchange/exchange2/src"/>
 	<classpathentry kind="src" path="packages/apps/Gallery2/src"/>
 	<classpathentry kind="src" path="packages/apps/Gallery2/src_pd"/>
 	<classpathentry kind="src" path="packages/apps/Gallery2/gallerycommon/src"/>
 	<classpathentry kind="src" path="packages/apps/HTMLViewer/src"/>
 	<classpathentry kind="src" path="packages/apps/Launcher2/src"/>
 	<classpathentry kind="src" path="packages/apps/Mms/src"/>
+	<classpathentry kind="src" path="packages/apps/Nfc/src"/>
+	<classpathentry kind="src" path="packages/apps/Nfc/nci/src"/>
 	<classpathentry kind="src" path="packages/apps/PackageInstaller/src"/>
 	<classpathentry kind="src" path="packages/apps/Phone/src"/>
 	<classpathentry kind="src" path="packages/apps/QuickSearchBox/src"/>
@@ -31,6 +33,9 @@
 	<classpathentry kind="src" path="packages/providers/DrmProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/MediaProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/TelephonyProvider/src"/>
+	<classpathentry kind="src" path="packages/screensavers/Basic/src"/>
+	<classpathentry kind="src" path="packages/screensavers/PhotoTable/src"/>
+	<classpathentry kind="src" path="packages/screensavers/WebView/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/am/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/input/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/pm/src"/>
@@ -41,12 +46,15 @@
 	<classpathentry kind="src" path="frameworks/base/icu4j/java"/>
 	<classpathentry kind="src" path="frameworks/base/keystore/java"/>
 	<classpathentry kind="src" path="frameworks/base/location/java"/>
+	<classpathentry kind="src" path="frameworks/base/location/lib/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/mca/effect/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/mca/filterfw/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/mca/filterpacks/java"/>
+	<classpathentry kind="src" path="frameworks/base/nfc-extras/java"/>
 	<classpathentry kind="src" path="frameworks/base/obex"/>
 	<classpathentry kind="src" path="frameworks/base/opengl/java"/>
+	<classpathentry kind="src" path="frameworks/base/packages/FusedLocation/src"/>
 	<classpathentry kind="src" path="frameworks/base/packages/SettingsProvider/src"/>
 	<classpathentry kind="src" path="frameworks/base/packages/SystemUI/src"/>
 	<classpathentry kind="src" path="frameworks/base/policy/src"/>
@@ -59,6 +67,7 @@
 	<classpathentry kind="src" path="frameworks/ex/carousel/java"/>
 	<classpathentry kind="src" path="frameworks/ex/chips/src"/>
 	<classpathentry kind="src" path="frameworks/ex/common/java"/>
+	<classpathentry kind="src" path="frameworks/ex/photoviewer/src"/>
 	<classpathentry kind="src" path="frameworks/ex/variablespeed/src"/>
 	<classpathentry kind="src" path="frameworks/opt/calendar/src"/>
 	<classpathentry kind="src" path="frameworks/opt/telephony/src/java"/>
@@ -90,6 +99,7 @@
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Browser_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/CalendarProvider_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/ContactsProvider_intermediates/src/src"/>
+	<classpathentry kind="src" path="out/target/common/obj/APPS/Gallery2_intermediates/src/renderscript/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/MediaProvider_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Music_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Phone_intermediates/src/src"/>
@@ -103,15 +113,17 @@
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/voip/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/wifi/java"/>
+	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/NfcLogTags_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/services_intermediates/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates/src/src/java"/>
 	<classpathentry kind="src" path="out/target/common/R"/>
 	<classpathentry kind="src" path="external/apache-http/src"/>
-	<classpathentry kind="src" path="external/bouncycastle/src/main/java"/>
+	<classpathentry kind="src" path="external/bouncycastle/bcprov/src/main/java"/>
 	<classpathentry kind="src" path="external/libphonenumber/java/src"/>
+	<classpathentry kind="src" path="external/mp4parser/isoparser/src/main/java"/>
 	<classpathentry kind="src" path="external/nist-sip/java"/>
 	<classpathentry kind="src" path="external/tagsoup/src"/>
-	<classpathentry excluding="src/" kind="src" path="out/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates/src"/>
+	<classpathentry kind="src" path="external/xmp_toolkit/XMPCore/src"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/bouncycastle_intermediates/classes-jarjar.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/android-support-v4_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/core-junit_intermediates/classes.jar"/>
@@ -119,5 +131,6 @@
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/android-common_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="packages/apps/Calculator/arity-2.1.2.jar"/>
+	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/junit-runner_intermediates/javalib.jar"/>
 	<classpathentry kind="output" path="out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes"/>
 </classpath>
diff --git a/ide/eclipse/android-include-paths.xml b/ide/eclipse/android-include-paths.xml
index bb038a2..0e60642 100644
--- a/ide/eclipse/android-include-paths.xml
+++ b/ide/eclipse/android-include-paths.xml
@@ -4,8 +4,7 @@
 <language name="holder for library settings">
 
 </language>
-<language name="GNU C++">
-<includepath>${ProjDirPath}/prebuilt/darwin-x86/toolchain/arm-linux-androideabi-4.4.x/lib/gcc/arm-linux-androideabi/4.4.3/include</includepath>
+<language name="Assembly">
 <includepath>${ProjDirPath}/system/core/include/arch/linux-arm</includepath>
 <includepath>${ProjDirPath}/system/core/include</includepath>
 <includepath>${ProjDirPath}/bionic/libc/arch-arm/include</includepath>
@@ -17,21 +16,41 @@
 <includepath>${ProjDirPath}/bionic/libm/include</includepath>
 <includepath>${ProjDirPath}/bionic/libm/include/arm</includepath>
 <includepath>${ProjDirPath}/bionic/libthread_db/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include/nativehelper</includepath>
 <includepath>${ProjDirPath}/hardware/libhardware/include</includepath>
 <includepath>${ProjDirPath}/hardware/libhardware_legacy/include</includepath>
 <includepath>${ProjDirPath}/hardware/ril/include</includepath>
+<includepath>${ProjDirPath}/frameworks/base/include</includepath>
 <includepath>${ProjDirPath}/frameworks/native/include</includepath>
 <includepath>${ProjDirPath}/frameworks/native/opengl/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/native/include</includepath>
 <includepath>${ProjDirPath}/external/skia/include</includepath>
 <includepath>${ProjDirPath}/external/skia/include/core</includepath>
+<includepath>${ProjDirPath}/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.6/lib/gcc/arm-linux-androideabi/4.6.x-google/include</includepath>
+
+</language>
+<language name="GNU C++">
+<includepath>${ProjDirPath}/system/core/include/arch/linux-arm</includepath>
+<includepath>${ProjDirPath}/system/core/include</includepath>
+<includepath>${ProjDirPath}/bionic/libc/arch-arm/include</includepath>
+<includepath>${ProjDirPath}/bionic/libc/include</includepath>
+<includepath>${ProjDirPath}/bionic/libstdc++/include</includepath>
+<includepath>${ProjDirPath}/bionic/libc/kernel/common</includepath>
+<includepath>${ProjDirPath}/bionic/libc/kernel/common/linux</includepath>
+<includepath>${ProjDirPath}/bionic/libc/kernel/arch-arm</includepath>
+<includepath>${ProjDirPath}/bionic/libm/include</includepath>
+<includepath>${ProjDirPath}/bionic/libm/include/arm</includepath>
+<includepath>${ProjDirPath}/bionic/libthread_db/include</includepath>
+<includepath>${ProjDirPath}/hardware/libhardware/include</includepath>
+<includepath>${ProjDirPath}/hardware/libhardware_legacy/include</includepath>
+<includepath>${ProjDirPath}/hardware/ril/include</includepath>
+<includepath>${ProjDirPath}/frameworks/base/include</includepath>
+<includepath>${ProjDirPath}/frameworks/native/include</includepath>
+<includepath>${ProjDirPath}/frameworks/native/opengl/include</includepath>
+<includepath>${ProjDirPath}/external/skia/include</includepath>
+<includepath>${ProjDirPath}/external/skia/include/core</includepath>
+<includepath>${ProjDirPath}/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.6/lib/gcc/arm-linux-androideabi/4.6.x-google/include</includepath>
 
 </language>
 <language name="GNU C">
-<includepath>${ProjDirPath}/prebuilt/darwin-x86/toolchain/arm-linux-androideabi-4.4.x/lib/gcc/arm-linux-androideabi/4.4.3/include</includepath>
 <includepath>${ProjDirPath}/system/core/include/arch/linux-arm</includepath>
 <includepath>${ProjDirPath}/system/core/include</includepath>
 <includepath>${ProjDirPath}/bionic/libc/arch-arm/include</includepath>
@@ -43,43 +62,15 @@
 <includepath>${ProjDirPath}/bionic/libm/include</includepath>
 <includepath>${ProjDirPath}/bionic/libm/include/arm</includepath>
 <includepath>${ProjDirPath}/bionic/libthread_db/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include/nativehelper</includepath>
 <includepath>${ProjDirPath}/hardware/libhardware/include</includepath>
 <includepath>${ProjDirPath}/hardware/libhardware_legacy/include</includepath>
 <includepath>${ProjDirPath}/hardware/ril/include</includepath>
+<includepath>${ProjDirPath}/frameworks/base/include</includepath>
 <includepath>${ProjDirPath}/frameworks/native/include</includepath>
 <includepath>${ProjDirPath}/frameworks/native/opengl/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/native/include</includepath>
 <includepath>${ProjDirPath}/external/skia/include</includepath>
 <includepath>${ProjDirPath}/external/skia/include/core</includepath>
-
-</language>
-<language name="Assembly">
-<includepath>${ProjDirPath}/prebuilt/darwin-x86/toolchain/arm-linux-androideabi-4.4.x/lib/gcc/arm-linux-androideabi/4.4.3/include</includepath>
-<includepath>${ProjDirPath}/system/core/include/arch/linux-arm</includepath>
-<includepath>${ProjDirPath}/system/core/include</includepath>
-<includepath>${ProjDirPath}/bionic/libc/arch-arm/include</includepath>
-<includepath>${ProjDirPath}/bionic/libc/include</includepath>
-<includepath>${ProjDirPath}/bionic/libstdc++/include</includepath>
-<includepath>${ProjDirPath}/bionic/libc/kernel/common</includepath>
-<includepath>${ProjDirPath}/bionic/libc/kernel/common/linux</includepath>
-<includepath>${ProjDirPath}/bionic/libc/kernel/arch-arm</includepath>
-<includepath>${ProjDirPath}/bionic/libm/include</includepath>
-<includepath>${ProjDirPath}/bionic/libm/include/arm</includepath>
-<includepath>${ProjDirPath}/bionic/libthread_db/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include</includepath>
-<includepath>${ProjDirPath}/dalvik/libnativehelper/include/nativehelper</includepath>
-<includepath>${ProjDirPath}/hardware/libhardware/include</includepath>
-<includepath>${ProjDirPath}/hardware/libhardware_legacy/include</includepath>
-<includepath>${ProjDirPath}/hardware/ril/include</includepath>
-<includepath>${ProjDirPath}/frameworks/native/include</includepath>
-<includepath>${ProjDirPath}/frameworks/native/opengl/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/include</includepath>
-<includepath>${ProjDirPath}/frameworks/base/native/include</includepath>
-<includepath>${ProjDirPath}/external/skia/include</includepath>
-<includepath>${ProjDirPath}/external/skia/include/core</includepath>
+<includepath>${ProjDirPath}/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.6/lib/gcc/arm-linux-androideabi/4.6.x-google/include</includepath>
 
 </language>
 </section>
diff --git a/ide/intellij/codestyles/AndroidStyle.xml b/ide/intellij/codestyles/AndroidStyle.xml
index 113ffca..cd6beb4 100644
--- a/ide/intellij/codestyles/AndroidStyle.xml
+++ b/ide/intellij/codestyles/AndroidStyle.xml
@@ -42,7 +42,7 @@
       <package name="" withSubpackages="true" />
     </value>
   </option>
-  <option name="RIGHT_MARGIN" value="80" />
+  <option name="RIGHT_MARGIN" value="100" />
   <option name="CALL_PARAMETERS_WRAP" value="1" />
   <option name="METHOD_PARAMETERS_WRAP" value="1" />
   <option name="EXTENDS_LIST_WRAP" value="1" />
diff --git a/ndk/platforms/android-9/samples/native-activity/Android.mk b/ndk/platforms/android-9/samples/native-activity/Android.mk
index 73b3d87..a092f41 100644
--- a/ndk/platforms/android-9/samples/native-activity/Android.mk
+++ b/ndk/platforms/android-9/samples/native-activity/Android.mk
@@ -39,7 +39,6 @@
 	jni/main.c \
 	../../../../sources/android/native_app_glue/android_native_app_glue.c
 
-LOCAL_NDK_VERSION := 4
 LOCAL_SDK_VERSION := 8
 
 LOCAL_SHARED_LIBRARIES := liblog libandroid libEGL libGLESv1_CM
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 54e9723..0273baa 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -36,7 +36,7 @@
     <!-- For android.media.audiofx.Visualizer -->
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="14" />
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="16" />
 
     <!-- We will request access to the camera, saying we require a camera
          of some sort but not one with autofocus capability. -->
@@ -47,7 +47,8 @@
     <application android:name="ApiDemosApplication"
             android:label="@string/activity_sample_code"
             android:icon="@drawable/app_sample_code"
-            android:hardwareAccelerated="true">
+            android:hardwareAccelerated="true"
+            android:supportsRtl="true">
 
         <!-- This is how we can request a library but still allow the app
              to be installed if it doesn't exist. -->
@@ -253,6 +254,46 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.PresentationActivity"
+                android:label="@string/activity_presentation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.PresentationWithMediaRouterActivity"
+                android:label="@string/activity_presentation_with_media_router">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.SecureWindowActivity"
+                android:label="@string/activity_secure_window">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.SecureDialogActivity"
+                android:label="@string/activity_secure_dialog">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.SecureSurfaceViewActivity"
+                android:label="@string/activity_secure_surface_view">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- Fragment Samples -->
 
         <activity android:name=".app.FragmentAlertDialog"
@@ -349,6 +390,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.FragmentNestingTabs"
+                android:label="@string/fragment_nesting_tabs"
+                android:enabled="@bool/atLeastJellyBeanMR1">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.FragmentRetainInstance"
                 android:label="@string/fragment_retain_instance"
                 android:enabled="@bool/atLeastHoneycomb">
@@ -1289,6 +1339,12 @@
         <!--      VIEW/WIDGET PACKAGE SAMPLES      -->
         <!-- ************************************* -->
 
+        <activity android:name=".view.TextClockDemo" android:label="Views/TextClock">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
         <activity android:name=".view.ChronometerDemo" android:label="Views/Chronometer">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1549,14 +1605,21 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".view.GridLayout0" android:label="Views/Layouts/GridLayout/0. Simple Form (Java)">
+        <activity android:name=".view.GridLayout1" android:label="Views/Layouts/GridLayout/1. Simple Form">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.SAMPLE_CODE" />
             </intent-filter>
         </activity>
 
-        <activity android:name=".view.GridLayout1" android:label="Views/Layouts/GridLayout/1. Simple Form (XML)">
+        <activity android:name=".view.GridLayout2" android:label="Views/Layouts/GridLayout/2. Form (XML)">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".view.GridLayout3" android:label="Views/Layouts/GridLayout/3. Form (Java)">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.SAMPLE_CODE" />
diff --git a/samples/ApiDemos/res/layout-land/grid_layout_2.xml b/samples/ApiDemos/res/layout-land/grid_layout_2.xml
new file mode 100644
index 0000000..b98b543
--- /dev/null
+++ b/samples/ApiDemos/res/layout-land/grid_layout_2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:useDefaultMargins="true"
+    android:alignmentMode="alignBounds"
+    android:rowOrderPreserved="false"
+    android:columnCount="4"
+    >
+    <TextView
+        android:text="Email setup"
+        android:textSize="32dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="center_horizontal"
+    />
+    <TextView
+        android:text="You can configure email in a few simple steps:"
+        android:textSize="16dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="left"
+    />
+    <TextView
+        android:text="Email address:"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="10"
+    />
+    <TextView
+        android:text="Password:"
+        android:layout_column="0"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="8"
+    />
+    <Button
+        android:text="Manual setup"
+        android:layout_row="5"
+        android:layout_column="3"
+    />
+    <Button
+        android:text="Next"
+        android:layout_column="3"
+        android:layout_gravity="fill_horizontal"
+    />
+</GridLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_arguments_fragment.xml b/samples/ApiDemos/res/layout/fragment_arguments_fragment.xml
new file mode 100644
index 0000000..b748239
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_arguments_fragment.xml
@@ -0,0 +1,50 @@
+<?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:orientation="vertical" android:padding="4dip"
+    android:gravity="center_horizontal"
+    android:layout_width="match_parent" android:layout_height="match_parent">
+
+    <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"
+            android:padding="4dip"
+            android:layout_gravity="center_vertical|center_horizontal"
+            android:gravity="top|center_horizontal"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="@string/fragment_arguments_fragment_msg" />
+
+    <LinearLayout android:orientation="horizontal" android:padding="4dip"
+        android:layout_width="match_parent" android:layout_height="wrap_content">
+
+    <FrameLayout
+            android:id="@+id/created1"
+            android:layout_width="0px"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+    <FrameLayout
+            android:id="@+id/created2"
+            android:layout_width="0px"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_pager_support.xml b/samples/ApiDemos/res/layout/fragment_pager_support.xml
deleted file mode 100644
index a082e2e..0000000
--- a/samples/ApiDemos/res/layout/fragment_pager_support.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Top-level content view for the simple fragment sample. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:orientation="vertical" android:padding="4dip"
-        android:gravity="center_horizontal"
-        android:layout_width="match_parent" android:layout_height="match_parent">
-
-    <android.support.v4.app.FragmentPager
-            android:id="@+id/pager"
-            android:layout_width="match_parent"
-            android:layout_height="0px"
-            android:layout_weight="1">
-    </android.support.v4.app.FragmentPager>
-
-    <LinearLayout android:orientation="horizontal"
-            android:gravity="center" android:measureWithLargestChild="true"
-            android:layout_width="match_parent" android:layout_height="wrap_content"
-            android:layout_weight="0">
-        <Button android:id="@+id/goto_first"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:text="@string/first">
-        </Button>
-        <Button android:id="@+id/goto_last"
-            android:layout_width="wrap_content" android:layout_height="wrap_content"
-            android:text="@string/last">
-        </Button>
-    </LinearLayout>
-</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_pager_support_list.xml b/samples/ApiDemos/res/layout/fragment_pager_support_list.xml
deleted file mode 100644
index bbe7b1d..0000000
--- a/samples/ApiDemos/res/layout/fragment_pager_support_list.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@android:drawable/gallery_thumb">
-
-    <TextView android:id="@+id/text"
-        android:layout_width="match_parent" android:layout_height="wrap_content"
-        android:gravity="center_vertical|center_horizontal"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:text="@string/hello_world"/>
-
-    <!-- The frame layout is here since we will be showing either
-    the empty view or the list view.  -->
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1" >
-        <!-- Here is the list. Since we are using a ListActivity, we
-             have to call it "@android:id/list" so ListActivity will
-             find it -->
-        <ListView android:id="@android:id/list"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:drawSelectorOnTop="false"/>
-
-        <!-- Here is the view to show if the list is emtpy -->
-        <TextView android:id="@android:id/empty"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:text="No items."/>
-
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_stack.xml b/samples/ApiDemos/res/layout/fragment_stack.xml
index 1d12496..6cf2fe6 100644
--- a/samples/ApiDemos/res/layout/fragment_stack.xml
+++ b/samples/ApiDemos/res/layout/fragment_stack.xml
@@ -28,11 +28,19 @@
             android:layout_weight="1">
     </FrameLayout>
 
-    <Button android:id="@+id/new_fragment"
+    <LinearLayout android:orientation="horizontal" android:padding="4dip"
+        android:gravity="center_horizontal" android:measureWithLargestChild="true"
         android:layout_width="wrap_content" android:layout_height="wrap_content"
-        android:layout_weight="0" 
-        android:text="@string/new_fragment">
-        <requestFocus />
-    </Button>
-
+        android:layout_weight="0">
+        <Button android:id="@+id/new_fragment"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="@string/new_fragment">
+            <requestFocus />
+        </Button>
+        <Button android:id="@+id/delete_fragment"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:layout_weight="0" 
+            android:text="@string/delete_fragment">
+        </Button>
+    </LinearLayout>
 </LinearLayout>
diff --git a/samples/ApiDemos/res/layout/fragment_tabs_fragment.xml b/samples/ApiDemos/res/layout/fragment_tabs_fragment.xml
new file mode 100644
index 0000000..89b2757
--- /dev/null
+++ b/samples/ApiDemos/res/layout/fragment_tabs_fragment.xml
@@ -0,0 +1,50 @@
+<?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.
+*/
+-->
+
+<TabHost
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/tabhost"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TabWidget
+            android:id="@android:id/tabs"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"/>
+
+        <FrameLayout
+            android:id="@android:id/tabcontent"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="0"/>
+
+        <FrameLayout
+            android:id="@+android:id/realtabcontent"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
+    </LinearLayout>
+</TabHost>
diff --git a/samples/ApiDemos/res/layout/grid_layout_1.xml b/samples/ApiDemos/res/layout/grid_layout_1.xml
index 0e53613..bd9a317 100644
--- a/samples/ApiDemos/res/layout/grid_layout_1.xml
+++ b/samples/ApiDemos/res/layout/grid_layout_1.xml
@@ -14,74 +14,33 @@
      limitations under the License.
 -->
 
+<!--
+    Demonstrates using GridLayout to build the "Simple Form" from the
+    LinearLayout and RelativeLayout demos.
+-->
+
 <GridLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-
-        android:useDefaultMargins="true"
-        android:alignmentMode="alignBounds"
-        android:rowOrderPreserved="false"
-
-        android:columnCount="4"
-        >
-
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/blue"
+    android:columnCount="4"
+    android:padding="10dip"
+    >
     <TextView
-            android:text="Email setup"
-            android:textSize="32dip"
-
-            android:layout_columnSpan="4"
-            android:layout_gravity="center_horizontal"
-            />
-
-    <TextView
-            android:text="You can configure email in just a few steps:"
-            android:textSize="16dip"
-
-            android:layout_columnSpan="4"
-            android:layout_gravity="left"
-            />
-
-    <TextView
-            android:text="Email address:"
-
-            android:layout_gravity="right"
-            />
-
+        android:text="@string/grid_layout_1_instructions"
+    />
     <EditText
-            android:ems="10"
-            />
-
-    <TextView
-            android:text="Password:"
-
-            android:layout_column="0"
-            android:layout_gravity="right"
-            />
-
-    <EditText
-            android:ems="8"
-            />
-
-    <Space
-            android:layout_row="2"
-            android:layout_rowSpan="3"
-            android:layout_column="2"
-            android:layout_gravity="fill"
-            />
-
+        android:layout_column="0"
+        android:layout_columnSpan="4"
+        android:layout_gravity="fill_horizontal"
+    />
     <Button
-            android:text="Manual setup"
-
-            android:layout_row="5"
-            android:layout_column="3"
-            />
-
+        android:layout_column="2"
+        android:text="@string/grid_layout_1_cancel"
+    />
     <Button
-            android:text="Next"
-
-            android:layout_column="3"
-            android:layout_gravity="fill_horizontal"
-            />
+        android:layout_marginLeft="10dip"
+        android:text="@string/grid_layout_1_ok"
+    />
 </GridLayout>
diff --git a/samples/ApiDemos/res/layout/grid_layout_2.xml b/samples/ApiDemos/res/layout/grid_layout_2.xml
new file mode 100644
index 0000000..880cb18
--- /dev/null
+++ b/samples/ApiDemos/res/layout/grid_layout_2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:useDefaultMargins="true"
+    android:alignmentMode="alignBounds"
+    android:columnOrderPreserved="false"
+    android:columnCount="4"
+    >
+    <TextView
+        android:text="Email setup"
+        android:textSize="32dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="center_horizontal"
+    />
+    <TextView
+        android:text="You can configure email in a few simple steps:"
+        android:textSize="16dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="left"
+    />
+    <TextView
+        android:text="Email address:"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="10"
+    />
+    <TextView
+        android:text="Password:"
+        android:layout_column="0"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="8"
+    />
+    <Button
+        android:text="Manual setup"
+        android:layout_row="5"
+        android:layout_column="3"
+    />
+    <Button
+        android:text="Next"
+        android:layout_column="3"
+        android:layout_gravity="fill_horizontal"
+    />
+</GridLayout>
diff --git a/samples/ApiDemos/res/layout/presentation_activity.xml b/samples/ApiDemos/res/layout/presentation_activity.xml
new file mode 100644
index 0000000..52c5022
--- /dev/null
+++ b/samples/ApiDemos/res/layout/presentation_activity.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<!-- Demonstrates an activity that shows content on secondary displays using
+     the android.app.Presentation class.
+     See corresponding Java code PresentationActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <!-- Message to show to use. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/presentation_activity_text"/>
+
+    <!-- A checkbox to toggle between showing all displays or only presentation displays. -->
+    <CheckBox android:id="@+id/show_all_displays"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:text="@string/presentation_show_all_displays" />
+
+    <!-- List that will show information about all connected displays. -->
+    <ListView android:id="@+id/display_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/presentation_content.xml b/samples/ApiDemos/res/layout/presentation_content.xml
new file mode 100644
index 0000000..6076aa6
--- /dev/null
+++ b/samples/ApiDemos/res/layout/presentation_content.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<!-- The content that we show on secondary displays.
+     See corresponding Java code PresentationActivity.java. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <!-- A picture for visual interest. -->
+    <ImageView android:id="@+id/image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <!-- A caption. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/ApiDemos/res/layout/presentation_list_item.xml b/samples/ApiDemos/res/layout/presentation_list_item.xml
new file mode 100644
index 0000000..d28b5d9
--- /dev/null
+++ b/samples/ApiDemos/res/layout/presentation_list_item.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+
+<!-- The content that we show on secondary displays.
+     See corresponding Java code PresentationActivity.java. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+
+    <CheckBox
+        android:id="@+id/checkbox_presentation"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="16dip"/>
+
+    <TextView android:id="@+id/display_id"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/checkbox_presentation"
+        android:layout_centerVertical="true"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <Button android:id="@+id/info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/presentation_info_text"/>
+
+</RelativeLayout>
diff --git a/samples/ApiDemos/res/layout/presentation_with_media_router_activity.xml b/samples/ApiDemos/res/layout/presentation_with_media_router_activity.xml
new file mode 100644
index 0000000..005002c
--- /dev/null
+++ b/samples/ApiDemos/res/layout/presentation_with_media_router_activity.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+<!-- See corresponding Java code PresentationWithMediaRouterActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <!-- Message to show to use. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/presentation_with_media_router_activity_text"/>
+
+    <!-- Some information about what's going on. -->
+    <TextView android:id="@+id/info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+    <!-- Some content for visual interest in the case where no presentation is showing. -->
+    <android.opengl.GLSurfaceView android:id="@+id/surface_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/presentation_with_media_router_content.xml b/samples/ApiDemos/res/layout/presentation_with_media_router_content.xml
new file mode 100644
index 0000000..8eb981b
--- /dev/null
+++ b/samples/ApiDemos/res/layout/presentation_with_media_router_content.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- The content that we show on secondary displays.
+     See corresponding Java code PresentationWithMediaRouterActivity.java. -->
+
+<android.opengl.GLSurfaceView  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/surface_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/samples/ApiDemos/res/layout/secure_dialog_activity.xml b/samples/ApiDemos/res/layout/secure_dialog_activity.xml
new file mode 100644
index 0000000..00457a4
--- /dev/null
+++ b/samples/ApiDemos/res/layout/secure_dialog_activity.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<!-- See corresponding Java code SecureDialogActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <!-- Message to show to use. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/secure_dialog_activity_text"/>
+
+    <!-- Button to show the dialog. -->
+    <Button android:id="@+id/show"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:text="@string/secure_dialog_show_button"/>
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/secure_surface_view_activity.xml b/samples/ApiDemos/res/layout/secure_surface_view_activity.xml
new file mode 100644
index 0000000..b7657b4
--- /dev/null
+++ b/samples/ApiDemos/res/layout/secure_surface_view_activity.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<!-- See corresponding Java code SecureSurfaceViewActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <!-- Message to show to use. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/secure_surface_view_activity_text"/>
+
+    <android.opengl.GLSurfaceView android:id="@+id/surface_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/secure_window_activity.xml b/samples/ApiDemos/res/layout/secure_window_activity.xml
new file mode 100644
index 0000000..6d086da
--- /dev/null
+++ b/samples/ApiDemos/res/layout/secure_window_activity.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<!-- See corresponding Java code SecureWindowActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+    <!-- Message to show to use. -->
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/secure_window_activity_text"/>
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/textclock.xml b/samples/ApiDemos/res/layout/textclock.xml
new file mode 100644
index 0000000..f86f616
--- /dev/null
+++ b/samples/ApiDemos/res/layout/textclock.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+
+<!-- Lots of buttons = need scrolling -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <!-- Default formats -->
+        <TextClock
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <!-- Shows the date only -->
+        <TextClock
+            android:format12Hour="MMM dd, yyyy"
+            android:format24Hour="MMM dd, yyyy"
+
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <!-- Use styles -->
+        <TextClock
+            android:format12Hour="@string/styled_12_hour_clock"
+            android:format24Hour="@string/styled_24_hour_clock"
+
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <!-- Shows seconds -->
+        <TextClock
+            android:format12Hour="hh:mm:ss aa"
+            android:format24Hour="kk:mm:ss"
+
+            android:textSize="20sp"
+            android:shadowColor="#7fffffff"
+            android:shadowRadius="3.0"
+
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <!-- Use a fixed time zone -->
+        <TextClock
+            android:format12Hour="'Time in Paris:' MMM dd, yyyy h:mmaa"
+            android:format24Hour="'Time in Paris:' MMM dd, yyyy k:mm"
+            android:timeZone="Europe/Paris"
+
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+    
+</ScrollView>
diff --git a/samples/ApiDemos/res/menu/presentation_with_media_router_menu.xml b/samples/ApiDemos/res/menu/presentation_with_media_router_menu.xml
new file mode 100644
index 0000000..08ef0dc
--- /dev/null
+++ b/samples/ApiDemos/res/menu/presentation_with_media_router_menu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/menu_media_route"
+        android:title="@string/presentation_with_media_router_play_on"
+        android:showAsAction="always"
+        android:orderInCategory="1"
+        android:actionProviderClass="android.app.MediaRouteActionProvider"/>
+</menu>
diff --git a/samples/ApiDemos/res/values-v17/bools.xml b/samples/ApiDemos/res/values-v17/bools.xml
new file mode 100644
index 0000000..41bb2c3
--- /dev/null
+++ b/samples/ApiDemos/res/values-v17/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- True if running under JellyBean MR1 or later. -->
+    <bool name="atLeastJellyBeanMR1">true</bool>
+</resources>
diff --git a/samples/ApiDemos/res/values/bools.xml b/samples/ApiDemos/res/values/bools.xml
index c112fb4..38c602e 100644
--- a/samples/ApiDemos/res/values/bools.xml
+++ b/samples/ApiDemos/res/values/bools.xml
@@ -34,4 +34,9 @@
          for JellyBean is true. -->
     <bool name="atLeastJellyBean">false</bool>
 
+    <!-- This resource is true if running under at least JellyBean MR1
+         API level.  The default value is false; an alternative value
+         for JellyBean MR 1 is true. -->
+    <bool name="atLeastJellyBeanMR1">false</bool>
+    
 </resources>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index a64de16..ee1241e 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -21,6 +21,9 @@
     <!--  app/activity examples strings  -->
     <!-- =============================== -->
 
+    <string name="styled_12_hour_clock">MM/<font fgcolor="#ffff0000"><b>dd</b></font>/yy h<font fgcolor="#ffff0000"><sup><small>mm</small></sup></font><b>aa</b></string>
+    <string name="styled_24_hour_clock">MM/<font fgcolor="#ffff0000"><b>dd</b></font>/yy k<font fgcolor="#ffff0000"><sup><small>mm</small></sup></font></string>
+
     <string name="tabs_1_tab_1">tab1</string>
     <string name="tabs_1_tab_2">tab2</string>
     <string name="tabs_1_tab_3">tab3</string>
@@ -112,6 +115,54 @@
     <string name="redirect_getter">Enter the text that will be used by the main activity.  Press back to cancel.</string>
     <string name="apply">Apply</string>
 
+    <string name="activity_presentation">App/Activity/Presentation</string>
+    <string name="presentation_activity_text">This activity demonstrates how to use a
+                  Presentation and the DisplayManager to show content on other Displays.\n
+                  Try connecting a secondary display and watch what happens.\n
+                  Selecting a Display will open a Presentation on it.</string>
+    <string name="presentation_show_all_displays">Show all displays</string>
+    <string name="presentation_photo_text">Showing photo #%1$d on display #%2$d: %3$s.</string>
+    <string name="presentation_info_text">Info</string>
+    <string name="presentation_display_id_text">Display #%1$d: %2$s</string>
+    <string name="presentation_alert_info_text">Display #%1$d Info</string>
+    <string name="presentation_alert_dismiss_text">OK</string>
+
+    <string name="activity_presentation_with_media_router">App/Activity/Presentation with Media Router</string>
+    <string name="presentation_with_media_router_activity_text">This activity demonstrates how to
+                  use a Presentation and the MediaRouter to automatically
+                  show content on a secondary display when available based on the currently
+                  selected media route.\n
+                  Try connecting a secondary display and watch what happens.</string>
+    <string name="presentation_with_media_router_play_on">Play on...</string>
+    <string name="presentation_with_media_router_now_playing_locally">Now playing on main display \'%s\'.</string>
+    <string name="presentation_with_media_router_now_playing_remotely">Now playing on secondary display \'%s\'.</string>
+
+    <string name="activity_secure_window">App/Activity/Secure Surfaces/Secure Window</string>
+    <string name="secure_window_activity_text">This activity demonstrates how to make an activity
+            use a secure surface so that its contents will only be visible on secure displays.
+            The activity\'s window has been marked with FLAG_SECURE to make it use a secure surface.
+            Consequently, the contents of the activity will not appear in screenshots and will not
+            be mirrored to non-secure displays.\n
+            \n
+            I am a secure activity!</string>
+
+    <string name="activity_secure_dialog">App/Activity/Secure Surfaces/Secure Dialog</string>
+    <string name="secure_dialog_activity_text">This activity demonstrates how to make a dialog use
+            a secure surface so that its contents will only be visible on secure displays.
+            The dialog\'s window has been marked with FLAG_SECURE to make it use a secure surface.
+            Consequently, the contents of the dialog will not appear in screenshots and will not
+            be mirrored to non-secure displays.</string>
+    <string name="secure_dialog_show_button">Show secure dialog</string>
+    <string name="secure_dialog_dialog_text">I am a secure dialog!</string>
+
+    <string name="activity_secure_surface_view">App/Activity/Secure Surfaces/Secure Surface View</string>
+    <string name="secure_surface_view_activity_text">This activity demonstrates how to make a
+            SurfaceView use a secure surface so that its contents will only be visible on
+            secure displays.
+            The surface view\'s window has been made secure using setSecure(true) to make it use
+            a secure surface.  Consequently, the contents of the surface view will not appear in
+            screenshots and will not be mirrored to non-secure displays.</string>
+
     <string name="fragment_alert_dialog">App/Fragment/Alert Dialog</string>
 
     <string name="fragment_arguments">App/Fragment/Arguments</string>
@@ -120,6 +171,9 @@
     <string name="fragment_arguments_embedded">From Attributes</string>
     <string name="fragment_arguments_embedded_land">Landscape Only</string>
 
+    <string name="fragment_arguments_fragment_msg">Demonstrates two embedded fragments
+        that are instantiated with arguments.</string>
+
     <string name="fragment_custom_animations">App/Fragment/Custom Animations</string>
 
     <string name="fragment_hide_show">App/Fragment/Hide and Show</string>
@@ -148,6 +202,8 @@
     <string name="fragment1menu">Show fragment 1 menu</string>
     <string name="fragment2menu">Show fragment 2 menu</string>
 
+    <string name="fragment_nesting_tabs">App/Fragment/Nesting Tabs</string>
+
     <string name="fragment_retain_instance">App/Fragment/Retain Instance</string>
     <string name="fragment_retain_instance_msg">Current progress of retained fragment;
     restarts if fragment is re-created.</string>
@@ -156,7 +212,8 @@
     <string name="fragment_receive_result">App/Fragment/Receive Result</string>
 
     <string name="fragment_stack">App/Fragment/Stack</string>
-    <string name="new_fragment">New fragment</string>
+    <string name="new_fragment">Push</string>
+    <string name="delete_fragment">Pop</string>
 
     <string name="first">First</string>
     <string name="last">Last</string>
@@ -661,6 +718,12 @@
     <string name="enable_admin">Enable admin</string>
     <string name="device_capabilities_category">Device capabilities</string>
     <string name="disable_camera">Disable all device cameras</string>
+    <string name="disable_keyguard_widgets">Disable keyguard widgets</string>
+    <string name="disable_keyguard_secure_camera">Disable keyguard secure camera</string>
+    <string name="keyguard_widgets_disabled">Keyguard widgets disabled</string>
+    <string name="keyguard_widgets_enabled">Keyguard widgets enabled</string>
+    <string name="keyguard_secure_camera_disabled">Keyguard secure camera disabled</string>
+    <string name="keyguard_secure_camera_enabled">Keyguard secure camera enabled</string>
     <string name="camera_disabled">Device cameras disabled</string>
     <string name="camera_enabled">Device cameras enabled</string>
     <string name="password_controls_category">Password controls</string>
@@ -1010,6 +1073,9 @@
     <string name="focus_5_button4">4</string>
     <string name="focus_5_button5">5</string>
     <string name="gallery_2_text">Testing</string>
+    <string name="grid_layout_1_instructions">Type here:</string>
+    <string name="grid_layout_1_ok">Ok</string>
+    <string name="grid_layout_1_cancel">Cancel</string>
     <string name="googlelogin_login">Login</string>
     <string name="googlelogin_bad_login">Bad Login</string>
     <string name="googlelogin_clear">Clear Credentials</string>
diff --git a/samples/ApiDemos/res/xml/device_admin_general.xml b/samples/ApiDemos/res/xml/device_admin_general.xml
index 61ccd32..3a1dd45 100644
--- a/samples/ApiDemos/res/xml/device_admin_general.xml
+++ b/samples/ApiDemos/res/xml/device_admin_general.xml
@@ -35,6 +35,14 @@
             android:key="key_disable_camera"
             android:title="@string/disable_camera" />
 
+        <CheckBoxPreference
+            android:key="key_disable_keyguard_widgets"
+            android:title="@string/disable_keyguard_widgets" />
+
+        <CheckBoxPreference
+            android:key="key_disable_keyguard_secure_camera"
+            android:title="@string/disable_keyguard_secure_camera" />
+
     </PreferenceCategory>
 
 </PreferenceScreen>
diff --git a/samples/ApiDemos/res/xml/device_admin_sample.xml b/samples/ApiDemos/res/xml/device_admin_sample.xml
index 2468919..4c8fc40 100644
--- a/samples/ApiDemos/res/xml/device_admin_sample.xml
+++ b/samples/ApiDemos/res/xml/device_admin_sample.xml
@@ -25,6 +25,7 @@
         <expire-password />
         <encrypted-storage />
         <disable-camera />
+        <disable-keyguard-features />
     </uses-policies>
 </device-admin>
 <!-- END_INCLUDE(meta_data) -->
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java
index 5585c91..257f0cd 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarDisplayOptions.java
@@ -93,18 +93,18 @@
             case R.id.cycle_custom_gravity:
                 ActionBar.LayoutParams lp = (ActionBar.LayoutParams) mCustomView.getLayoutParams();
                 int newGravity = 0;
-                switch (lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.LEFT:
+                switch (lp.gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.START:
                         newGravity = Gravity.CENTER_HORIZONTAL;
                         break;
                     case Gravity.CENTER_HORIZONTAL:
-                        newGravity = Gravity.RIGHT;
+                        newGravity = Gravity.END;
                         break;
-                    case Gravity.RIGHT:
-                        newGravity = Gravity.LEFT;
+                    case Gravity.END:
+                        newGravity = Gravity.START;
                         break;
                 }
-                lp.gravity = lp.gravity & ~Gravity.HORIZONTAL_GRAVITY_MASK | newGravity;
+                lp.gravity = lp.gravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK | newGravity;
                 bar.setCustomView(mCustomView, lp);
                 return;
         }
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
index 324b8ce..db50185 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
@@ -68,6 +68,9 @@
     // The following keys are used to find each preference item
     private static final String KEY_ENABLE_ADMIN = "key_enable_admin";
     private static final String KEY_DISABLE_CAMERA = "key_disable_camera";
+    private static final String KEY_DISABLE_KEYGUARD_WIDGETS = "key_disable_keyguard_widgets";
+    private static final String KEY_DISABLE_KEYGUARD_SECURE_CAMERA
+            = "key_disable_keyguard_secure_camera";
 
     private static final String KEY_CATEGORY_QUALITY = "key_category_quality";
     private static final String KEY_SET_PASSWORD = "key_set_password";
@@ -245,6 +248,8 @@
         // UI elements
         private CheckBoxPreference mEnableCheckbox;
         private CheckBoxPreference mDisableCameraCheckbox;
+        private CheckBoxPreference mDisableKeyguardWidgetsCheckbox;
+        private CheckBoxPreference mDisableKeyguardSecureCameraCheckbox;
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -254,6 +259,12 @@
             mEnableCheckbox.setOnPreferenceChangeListener(this);
             mDisableCameraCheckbox = (CheckBoxPreference) findPreference(KEY_DISABLE_CAMERA);
             mDisableCameraCheckbox.setOnPreferenceChangeListener(this);
+            mDisableKeyguardWidgetsCheckbox =
+                (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_WIDGETS);
+            mDisableKeyguardWidgetsCheckbox.setOnPreferenceChangeListener(this);
+            mDisableKeyguardSecureCameraCheckbox =
+                (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_SECURE_CAMERA);
+            mDisableKeyguardSecureCameraCheckbox.setOnPreferenceChangeListener(this);
         }
 
         // At onResume time, reload UI with current values as required
@@ -265,10 +276,20 @@
 
             if (mAdminActive) {
                 mDPM.setCameraDisabled(mDeviceAdminSample, mDisableCameraCheckbox.isChecked());
+                mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, createKeyguardDisabledFlag());
                 reloadSummaries();
             }
         }
 
+        int createKeyguardDisabledFlag() {
+            int flags = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+            flags |= mDisableKeyguardWidgetsCheckbox.isChecked() ?
+                    DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL : 0;
+            flags |= mDisableKeyguardSecureCameraCheckbox.isChecked() ?
+                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA : 0;
+            return flags;
+        }
+
         @Override
         public boolean onPreferenceChange(Preference preference, Object newValue) {
             if (super.onPreferenceChange(preference, newValue)) {
@@ -295,6 +316,10 @@
             } else if (preference == mDisableCameraCheckbox) {
                 mDPM.setCameraDisabled(mDeviceAdminSample, value);
                 reloadSummaries();
+            } else if (preference == mDisableKeyguardWidgetsCheckbox
+                    || preference == mDisableKeyguardSecureCameraCheckbox) {
+                mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, createKeyguardDisabledFlag());
+                reloadSummaries();
             }
             return true;
         }
@@ -305,11 +330,25 @@
             String cameraSummary = getString(mDPM.getCameraDisabled(mDeviceAdminSample)
                     ? R.string.camera_disabled : R.string.camera_enabled);
             mDisableCameraCheckbox.setSummary(cameraSummary);
+
+            int disabled = mDPM.getKeyguardDisabledFeatures(mDeviceAdminSample);
+
+            String keyguardWidgetSummary = getString(
+                    (disabled & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0 ?
+                            R.string.keyguard_widgets_disabled : R.string.keyguard_widgets_enabled);
+            mDisableKeyguardWidgetsCheckbox.setSummary(keyguardWidgetSummary);
+
+            String keyguardSecureCameraSummary = getString(
+                (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 ?
+                R.string.keyguard_secure_camera_disabled : R.string.keyguard_secure_camera_enabled);
+            mDisableKeyguardSecureCameraCheckbox.setSummary(keyguardSecureCameraSummary);
         }
 
         /** Updates the device capabilities area (dis/enabling) as the admin is (de)activated */
         private void enableDeviceCapabilitiesArea(boolean enabled) {
             mDisableCameraCheckbox.setEnabled(enabled);
+            mDisableKeyguardWidgetsCheckbox.setEnabled(enabled);
+            mDisableKeyguardSecureCameraCheckbox.setEnabled(enabled);
         }
     }
 
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java
new file mode 100644
index 0000000..7023c43
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentArgumentsFragment.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Demonstrates a fragment that can be configured through both Bundle arguments
+ * and layout attributes.
+ */
+public class FragmentArgumentsFragment extends Fragment {
+    @Override public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            // First-time init; create fragment to embed in activity.
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+            Fragment newFragment = FragmentArguments.MyFragment.newInstance("From Arguments 1");
+            ft.add(R.id.created1, newFragment);
+            newFragment = FragmentArguments.MyFragment.newInstance("From Arguments 2");
+            ft.add(R.id.created2, newFragment);
+            ft.commit();
+        }
+    }
+
+    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_arguments_fragment, container, false);
+        return v;
+    }
+//END_INCLUDE(create)
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java
index 6bc73e0..420e67f 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentContextMenu.java
@@ -29,6 +29,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.Toast;
 
 /**
  * Demonstration of displaying a context menu from a fragment.
@@ -65,10 +66,10 @@
         public boolean onContextItemSelected(MenuItem item) {
             switch (item.getItemId()) {
                 case R.id.a_item:
-                    Log.i("ContextMenu", "Item 1a was chosen");
+                    Toast.makeText(getActivity(), "Item 1a was chosen", Toast.LENGTH_SHORT).show();
                     return true;
                 case R.id.b_item:
-                    Log.i("ContextMenu", "Item 1b was chosen");
+                    Toast.makeText(getActivity(), "Item 1b was chosen", Toast.LENGTH_SHORT).show();
                     return true;
             }
             return super.onContextItemSelected(item);
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
index 730f4d4..572173f 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java
@@ -150,7 +150,11 @@
                     // Execute a transaction, replacing any existing fragment
                     // with this one inside the frame.
                     FragmentTransaction ft = getFragmentManager().beginTransaction();
-                    ft.replace(R.id.details, details);
+                    if (index == 0) {
+                        ft.replace(R.id.details, details);
+                    } else {
+                        ft.replace(R.id.a_item, details);
+                    }
                     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                     ft.commit();
                 }
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java
new file mode 100644
index 0000000..6ac7003
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentMenuFragment.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+
+/**
+ * Demonstrates how fragments can participate in the options menu.
+ */
+public class FragmentMenuFragment extends Fragment {
+    Fragment mFragment1;
+    Fragment mFragment2;
+    CheckBox mCheckBox1;
+    CheckBox mCheckBox2;
+
+    // Update fragment visibility when check boxes are changed.
+    final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            updateFragmentVisibility();
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_menu, container, false);
+
+        // Make sure the two menu fragments are created.
+        FragmentManager fm = getChildFragmentManager();
+        FragmentTransaction ft = fm.beginTransaction();
+        mFragment1 = fm.findFragmentByTag("f1");
+        if (mFragment1 == null) {
+            mFragment1 = new FragmentMenu.MenuFragment();
+            ft.add(mFragment1, "f1");
+        }
+        mFragment2 = fm.findFragmentByTag("f2");
+        if (mFragment2 == null) {
+            mFragment2 = new FragmentMenu.Menu2Fragment();
+            ft.add(mFragment2, "f2");
+        }
+        ft.commit();
+        
+        // Watch check box clicks.
+        mCheckBox1 = (CheckBox)v.findViewById(R.id.menu1);
+        mCheckBox1.setOnClickListener(mClickListener);
+        mCheckBox2 = (CheckBox)v.findViewById(R.id.menu2);
+        mCheckBox2.setOnClickListener(mClickListener);
+        
+        // Make sure fragments start out with correct visibility.
+        updateFragmentVisibility();
+
+        return v;
+    }
+
+    @Override
+    public void onViewStateRestored(Bundle savedInstanceState) {
+        super.onViewStateRestored(savedInstanceState);
+        // Make sure fragments are updated after check box view state is restored.
+        updateFragmentVisibility();
+    }
+
+    // Update fragment visibility based on current check box state.
+    void updateFragmentVisibility() {
+        FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+        if (mCheckBox1.isChecked()) ft.show(mFragment1);
+        else ft.hide(mFragment1);
+        if (mCheckBox2.isChecked()) ft.show(mFragment2);
+        else ft.hide(mFragment2);
+        ft.commit();
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java
new file mode 100644
index 0000000..4331c2a
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentNestingTabs.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.apis.app;
+
+//BEGIN_INCLUDE(complete)
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.widget.Toast;
+
+/**
+ * This demonstrates the use of action bar tabs and how they interact
+ * with other action bar features.
+ */
+public class FragmentNestingTabs extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        FragmentManager.enableDebugLogging(true);
+        super.onCreate(savedInstanceState);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+
+        bar.addTab(bar.newTab()
+                .setText("Menus")
+                .setTabListener(new TabListener<FragmentMenuFragment>(
+                        this, "menus", FragmentMenuFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Args")
+                .setTabListener(new TabListener<FragmentArgumentsFragment>(
+                        this, "args", FragmentArgumentsFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Stack")
+                .setTabListener(new TabListener<FragmentStackFragment>(
+                        this, "stack", FragmentStackFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Tabs")
+                .setTabListener(new TabListener<FragmentTabsFragment>(
+                        this, "tabs", FragmentTabsFragment.class)));
+
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+        private final Activity mActivity;
+        private final String mTag;
+        private final Class<T> mClass;
+        private final Bundle mArgs;
+        private Fragment mFragment;
+
+        public TabListener(Activity activity, String tag, Class<T> clz) {
+            this(activity, tag, clz, null);
+        }
+
+        public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
+            mActivity = activity;
+            mTag = tag;
+            mClass = clz;
+            mArgs = args;
+
+            // Check to see if we already have a fragment for this tab, probably
+            // from a previously saved state.  If so, deactivate it, because our
+            // initial state is that a tab isn't shown.
+            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
+            if (mFragment != null && !mFragment.isDetached()) {
+                FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
+                ft.detach(mFragment);
+                ft.commit();
+            }
+        }
+
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            if (mFragment == null) {
+                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
+                ft.add(android.R.id.content, mFragment, mTag);
+            } else {
+                ft.attach(mFragment);
+            }
+        }
+
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            if (mFragment != null) {
+                ft.detach(mFragment);
+            }
+        }
+
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
+        }
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
index 891eda4..242d670 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStack.java
@@ -44,6 +44,12 @@
                 addFragmentToStack();
             }
         });
+        button = (Button)findViewById(R.id.delete_fragment);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                getFragmentManager().popBackStack();
+            }
+        });
 
         if (savedInstanceState == null) {
             // Do first time initialization -- add initial fragment.
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java
new file mode 100644
index 0000000..d33af64
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentStackFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class FragmentStackFragment extends Fragment {
+    int mStackLevel = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            // Do first time initialization -- add initial fragment.
+            Fragment newFragment = FragmentStack.CountingFragment.newInstance(mStackLevel);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+            ft.add(R.id.simple_fragment, newFragment).commit();
+        } else {
+            mStackLevel = savedInstanceState.getInt("level");
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_stack, container, false);
+
+        // Watch for button clicks.
+        Button button = (Button)v.findViewById(R.id.new_fragment);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                addFragmentToStack();
+            }
+        });
+        button = (Button)v.findViewById(R.id.delete_fragment);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                getChildFragmentManager().popBackStack();
+            }
+        });
+
+        return v;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("level", mStackLevel);
+    }
+
+    void addFragmentToStack() {
+        mStackLevel++;
+
+        // Instantiate a new fragment.
+        Fragment newFragment = FragmentStack.CountingFragment.newInstance(mStackLevel);
+
+        // Add the fragment to the activity, pushing this transaction
+        // on to the back stack.
+        FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+        ft.replace(R.id.simple_fragment, newFragment);
+        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        ft.addToBackStack(null);
+        ft.commit();
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java
new file mode 100644
index 0000000..1d45e4d
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabsFragment.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.apis.app;
+
+import java.util.ArrayList;
+
+import com.example.android.apis.R;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TabHost;
+
+/**
+ * Sample fragment that contains tabs of other fragments.
+ */
+public class FragmentTabsFragment extends Fragment {
+    TabManager mTabManager;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mTabManager = new TabManager(getActivity(), getChildFragmentManager(),
+                R.id.realtabcontent);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_tabs_fragment, container, false);
+        TabHost host = mTabManager.handleCreateView(v);
+
+        mTabManager.addTab(host.newTabSpec("result").setIndicator("Result"),
+                FragmentReceiveResult.ReceiveResultFragment.class, null);
+        mTabManager.addTab(host.newTabSpec("contacts").setIndicator("Contacts"),
+                LoaderCursor.CursorLoaderListFragment.class, null);
+        mTabManager.addTab(host.newTabSpec("apps").setIndicator("Apps"),
+                LoaderCustom.AppListFragment.class, null);
+        mTabManager.addTab(host.newTabSpec("throttle").setIndicator("Throttle"),
+                LoaderThrottle.ThrottledLoaderListFragment.class, null);
+
+        return v;
+    }
+
+    @Override
+    public void onViewStateRestored(Bundle savedInstanceState) {
+        super.onViewStateRestored(savedInstanceState);
+        mTabManager.handleViewStateRestored(savedInstanceState);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mTabManager.handleDestroyView();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mTabManager.handleSaveInstanceState(outState);
+    }
+
+    /**
+     * This is a helper class that implements a generic mechanism for
+     * associating fragments with the tabs in a tab host.  DO NOT USE THIS.
+     * If you want tabs in a fragment, use the support v13 library's
+     * FragmentTabHost class, which takes care of all of this for you (in
+     * a simpler way even).
+     */
+    public static class TabManager implements TabHost.OnTabChangeListener {
+        private final Context mContext;
+        private final FragmentManager mManager;
+        private final int mContainerId;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+        private TabHost mTabHost;
+        private TabInfo mLastTab;
+        private boolean mInitialized;
+        private String mCurrentTabTag;
+
+        static final class TabInfo {
+            private final String tag;
+            private final Class<?> clss;
+            private final Bundle args;
+            private Fragment fragment;
+
+            TabInfo(String _tag, Class<?> _class, Bundle _args) {
+                tag = _tag;
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        static class DummyTabFactory implements TabHost.TabContentFactory {
+            private final Context mContext;
+
+            public DummyTabFactory(Context context) {
+                mContext = context;
+            }
+
+            @Override
+            public View createTabContent(String tag) {
+                View v = new View(mContext);
+                v.setMinimumWidth(0);
+                v.setMinimumHeight(0);
+                return v;
+            }
+        }
+
+        public TabManager(Context context, FragmentManager manager, int containerId) {
+            mContext = context;
+            mManager = manager;
+            mContainerId = containerId;
+        }
+
+        public TabHost handleCreateView(View root) {
+            if (mTabHost != null) {
+                throw new IllegalStateException("TabHost already set");
+            }
+            mTabHost = (TabHost)root.findViewById(android.R.id.tabhost);
+            mTabHost.setup();
+            mTabHost.setOnTabChangedListener(this);
+            return mTabHost;
+        }
+
+        public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
+            tabSpec.setContent(new DummyTabFactory(mContext));
+            String tag = tabSpec.getTag();
+            TabInfo info = new TabInfo(tag, clss, args);
+            mTabs.add(info);
+            mTabHost.addTab(tabSpec);
+        }
+
+        public void handleViewStateRestored(Bundle savedInstanceState) {
+            if (savedInstanceState != null) {
+                mCurrentTabTag = savedInstanceState.getString("tab");
+            }
+            mTabHost.setCurrentTabByTag(mCurrentTabTag);
+
+            String currentTab = mTabHost.getCurrentTabTag();
+
+            // Go through all tabs and make sure their fragments match
+            // the correct state.
+            FragmentTransaction ft = null;
+            for (int i=0; i<mTabs.size(); i++) {
+                TabInfo tab = mTabs.get(i);
+                tab.fragment = mManager.findFragmentByTag(tab.tag);
+                if (tab.fragment != null && !tab.fragment.isDetached()) {
+                    if (tab.tag.equals(currentTab)) {
+                        // The fragment for this tab is already there and
+                        // active, and it is what we really want to have
+                        // as the current tab.  Nothing to do.
+                        mLastTab = tab;
+                    } else {
+                        // This fragment was restored in the active state,
+                        // but is not the current tab.  Deactivate it.
+                        if (ft == null) {
+                            ft = mManager.beginTransaction();
+                        }
+                        ft.detach(tab.fragment);
+                    }
+                }
+            }
+
+            // We are now ready to go.  Make sure we are switched to the
+            // correct tab.
+            mInitialized = true;
+            ft = doTabChanged(currentTab, ft);
+            if (ft != null) {
+                ft.commit();
+                mManager.executePendingTransactions();
+            }
+        }
+
+        public void handleDestroyView() {
+            mCurrentTabTag = mTabHost.getCurrentTabTag();
+            mTabHost = null;
+            mTabs.clear();
+            mInitialized = false;
+        }
+
+        public void handleSaveInstanceState(Bundle outState) {
+            outState.putString("tab", mTabHost != null
+                    ? mTabHost.getCurrentTabTag() : mCurrentTabTag);
+        }
+
+        @Override
+        public void onTabChanged(String tabId) {
+            if (!mInitialized) {
+                return;
+            }
+            FragmentTransaction ft = doTabChanged(tabId, null);
+            if (ft != null) {
+                ft.commit();
+            }
+        }
+
+        private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
+            TabInfo newTab = null;
+            for (int i=0; i<mTabs.size(); i++) {
+                TabInfo tab = mTabs.get(i);
+                if (tab.tag.equals(tabId)) {
+                    newTab = tab;
+                }
+            }
+            if (newTab == null) {
+                throw new IllegalStateException("No tab known for tag " + tabId);
+            }
+            if (mLastTab != newTab) {
+                if (ft == null) {
+                    ft = mManager.beginTransaction();
+                }
+                if (mLastTab != null) {
+                    if (mLastTab.fragment != null) {
+                        ft.detach(mLastTab.fragment);
+                    }
+                }
+                if (newTab != null) {
+                    if (newTab.fragment == null) {
+                        newTab.fragment = Fragment.instantiate(mContext,
+                                newTab.clss.getName(), newTab.args);
+                        ft.add(mContainerId, newTab.fragment, newTab.tag);
+                    } else {
+                        ft.attach(newTab.fragment);
+                    }
+                }
+
+                mLastTab = newTab;
+            }
+            return ft;
+        }
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
new file mode 100644
index 0000000..c626022
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+// Need the following import to get access to the app resources, since this
+// class is in a sub-package.
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.drawable.GradientDrawable;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Parcelable.Creator;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+//BEGIN_INCLUDE(activity)
+/**
+ * <h3>Presentation Activity</h3>
+ *
+ * <p>
+ * This demonstrates how to create an activity that shows some content
+ * on a secondary display using a {@link Presentation}.
+ * </p><p>
+ * The activity uses the {@link DisplayManager} API to enumerate displays.
+ * When the user selects a display, the activity opens a {@link Presentation}
+ * on that display.  We show a different photograph in each presentation
+ * on a unique background along with a label describing the display.
+ * We also write information about displays and display-related events to
+ * the Android log which you can read using <code>adb logcat</code>.
+ * </p><p>
+ * You can try this out using an HDMI or Wifi display or by using the
+ * "Simulate secondary displays" feature in Development Settings to create a few
+ * simulated secondary displays.  Each display will appear in the list along with a
+ * checkbox to show a presentation on that display.
+ * </p><p>
+ * See also the {@link PresentationWithMediaRouterActivity} sample which
+ * uses the media router to automatically select a secondary display
+ * on which to show content based on the currently selected route.
+ * </p>
+ */
+public class PresentationActivity extends Activity
+        implements OnCheckedChangeListener, OnClickListener {
+    private final String TAG = "PresentationActivity";
+
+    // Key for storing saved instance state.
+    private static final String PRESENTATION_KEY = "presentation";
+
+    // The content that we want to show on the presentation.
+    private static final int[] PHOTOS = new int[] {
+        R.drawable.frantic,
+        R.drawable.photo1, R.drawable.photo2, R.drawable.photo3,
+        R.drawable.photo4, R.drawable.photo5, R.drawable.photo6,
+        R.drawable.sample_4,
+    };
+
+    private DisplayManager mDisplayManager;
+    private DisplayListAdapter mDisplayListAdapter;
+    private CheckBox mShowAllDisplaysCheckbox;
+    private ListView mListView;
+    private int mNextImageNumber;
+
+    // List of presentation contents indexed by displayId.
+    // This state persists so that we can restore the old presentation
+    // contents when the activity is paused or resumed.
+    private SparseArray<PresentationContents> mSavedPresentationContents;
+
+    // List of all currently visible presentations indexed by display id.
+    private final SparseArray<DemoPresentation> mActivePresentations =
+            new SparseArray<DemoPresentation>();
+
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        // Restore saved instance state.
+        if (savedInstanceState != null) {
+            mSavedPresentationContents =
+                    savedInstanceState.getSparseParcelableArray(PRESENTATION_KEY);
+        } else {
+            mSavedPresentationContents = new SparseArray<PresentationContents>();
+        }
+
+        // Get the display manager service.
+        mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
+
+        // See assets/res/any/layout/presentation_activity.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.presentation_activity);
+
+        // Set up checkbox to toggle between showing all displays or only presentation displays.
+        mShowAllDisplaysCheckbox = (CheckBox)findViewById(R.id.show_all_displays);
+        mShowAllDisplaysCheckbox.setOnCheckedChangeListener(this);
+
+        // Set up the list of displays.
+        mDisplayListAdapter = new DisplayListAdapter(this);
+        mListView = (ListView)findViewById(R.id.display_list);
+        mListView.setAdapter(mDisplayListAdapter);
+    }
+
+    @Override
+    protected void onResume() {
+        // Be sure to call the super class.
+        super.onResume();
+
+        // Update our list of displays on resume.
+        mDisplayListAdapter.updateContents();
+
+        // Restore presentations from before the activity was paused.
+        final int numDisplays = mDisplayListAdapter.getCount();
+        for (int i = 0; i < numDisplays; i++) {
+            final Display display = mDisplayListAdapter.getItem(i);
+            final PresentationContents contents =
+                    mSavedPresentationContents.get(display.getDisplayId());
+            if (contents != null) {
+                showPresentation(display, contents);
+            }
+        }
+        mSavedPresentationContents.clear();
+
+        // Register to receive events from the display manager.
+        mDisplayManager.registerDisplayListener(mDisplayListener, null);
+    }
+
+    @Override
+    protected void onPause() {
+        // Be sure to call the super class.
+        super.onPause();
+
+        // Unregister from the display manager.
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+
+        // Dismiss all of our presentations but remember their contents.
+        Log.d(TAG, "Activity is being paused.  Dismissing all active presentation.");
+        for (int i = 0; i < mActivePresentations.size(); i++) {
+            DemoPresentation presentation = mActivePresentations.valueAt(i);
+            int displayId = mActivePresentations.keyAt(i);
+            mSavedPresentationContents.put(displayId, presentation.mContents);
+            presentation.dismiss();
+        }
+        mActivePresentations.clear();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        // Be sure to call the super class.
+        super.onSaveInstanceState(outState);
+        outState.putSparseParcelableArray(PRESENTATION_KEY, mSavedPresentationContents);
+    }
+
+    /**
+     * Shows a {@link Presentation} on the specified display.
+     */
+    private void showPresentation(Display display, PresentationContents contents) {
+        final int displayId = display.getDisplayId();
+        if (mActivePresentations.get(displayId) != null) {
+            return;
+        }
+
+        Log.d(TAG, "Showing presentation photo #" + contents.photo
+                + " on display #" + displayId + ".");
+
+        DemoPresentation presentation = new DemoPresentation(this, display, contents);
+        presentation.show();
+        presentation.setOnDismissListener(mOnDismissListener);
+        mActivePresentations.put(displayId, presentation);
+    }
+
+    /**
+     * Hides a {@link Presentation} on the specified display.
+     */
+    private void hidePresentation(Display display) {
+        final int displayId = display.getDisplayId();
+        DemoPresentation presentation = mActivePresentations.get(displayId);
+        if (presentation == null) {
+            return;
+        }
+
+        Log.d(TAG, "Dismissing presentation on display #" + displayId + ".");
+
+        presentation.dismiss();
+        mActivePresentations.delete(displayId);
+    }
+
+    private int getNextPhoto() {
+        final int photo = mNextImageNumber;
+        mNextImageNumber = (mNextImageNumber + 1) % PHOTOS.length;
+        return photo;
+    }
+
+    /**
+     * Called when the show all displays checkbox is toggled or when
+     * an item in the list of displays is checked or unchecked.
+     */
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView == mShowAllDisplaysCheckbox) {
+            // Show all displays checkbox was toggled.
+            mDisplayListAdapter.updateContents();
+        } else {
+            // Display item checkbox was toggled.
+            final Display display = (Display)buttonView.getTag();
+            if (isChecked) {
+                PresentationContents contents = new PresentationContents(getNextPhoto());
+                showPresentation(display, contents);
+            } else {
+                hidePresentation(display);
+            }
+        }
+    }
+
+    /**
+     * Called when the Info button next to a display is clicked to show information
+     * about the display.
+     */
+    @Override
+    public void onClick(View v) {
+        Context context = v.getContext();
+        AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        final Display display = (Display)v.getTag();
+        Resources r = context.getResources();
+        AlertDialog alert = builder
+                .setTitle(r.getString(
+                        R.string.presentation_alert_info_text, display.getDisplayId()))
+                .setMessage(display.toString())
+                .setNeutralButton(R.string.presentation_alert_dismiss_text,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                            }
+                    })
+                .create();
+        alert.show();
+    }
+
+    /**
+     * Listens for displays to be added, changed or removed.
+     * We use it to update the list and show a new {@link Presentation} when a
+     * display is connected.
+     *
+     * Note that we don't bother dismissing the {@link Presentation} when a
+     * display is removed, although we could.  The presentation API takes care
+     * of doing that automatically for us.
+     */
+    private final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+            Log.d(TAG, "Display #" + displayId + " added.");
+            mDisplayListAdapter.updateContents();
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            Log.d(TAG, "Display #" + displayId + " changed.");
+            mDisplayListAdapter.updateContents();
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            Log.d(TAG, "Display #" + displayId + " removed.");
+            mDisplayListAdapter.updateContents();
+        }
+    };
+
+    /**
+     * Listens for when presentations are dismissed.
+     */
+    private final DialogInterface.OnDismissListener mOnDismissListener =
+            new DialogInterface.OnDismissListener() {
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            DemoPresentation presentation = (DemoPresentation)dialog;
+            int displayId = presentation.getDisplay().getDisplayId();
+            Log.d(TAG, "Presentation on display #" + displayId + " was dismissed.");
+            mActivePresentations.delete(displayId);
+            mDisplayListAdapter.notifyDataSetChanged();
+        }
+    };
+
+    /**
+     * List adapter.
+     * Shows information about all displays.
+     */
+    private final class DisplayListAdapter extends ArrayAdapter<Display> {
+        final Context mContext;
+
+        public DisplayListAdapter(Context context) {
+            super(context, R.layout.presentation_list_item);
+            mContext = context;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View v;
+            if (convertView == null) {
+                v = ((Activity) mContext).getLayoutInflater().inflate(
+                        R.layout.presentation_list_item, null);
+            } else {
+                v = convertView;
+            }
+
+            final Display display = getItem(position);
+            final int displayId = display.getDisplayId();
+
+            CheckBox cb = (CheckBox)v.findViewById(R.id.checkbox_presentation);
+            cb.setTag(display);
+            cb.setOnCheckedChangeListener(PresentationActivity.this);
+            cb.setChecked(mActivePresentations.indexOfKey(displayId) >= 0
+                    || mSavedPresentationContents.indexOfKey(displayId) >= 0);
+
+            TextView tv = (TextView)v.findViewById(R.id.display_id);
+            tv.setText(v.getContext().getResources().getString(
+                    R.string.presentation_display_id_text, displayId, display.getName()));
+
+            Button b = (Button)v.findViewById(R.id.info);
+            b.setTag(display);
+            b.setOnClickListener(PresentationActivity.this);
+
+            return v;
+        }
+
+        /**
+         * Update the contents of the display list adapter to show
+         * information about all current displays.
+         */
+        public void updateContents() {
+            clear();
+
+            String displayCategory = getDisplayCategory();
+            Display[] displays = mDisplayManager.getDisplays(displayCategory);
+            addAll(displays);
+
+            Log.d(TAG, "There are currently " + displays.length + " displays connected.");
+            for (Display display : displays) {
+                Log.d(TAG, "  " + display);
+            }
+        }
+
+        private String getDisplayCategory() {
+            return mShowAllDisplaysCheckbox.isChecked() ? null :
+                DisplayManager.DISPLAY_CATEGORY_PRESENTATION;
+        }
+    }
+
+    /**
+     * The presentation to show on the secondary display.
+     *
+     * Note that the presentation display may have different metrics from the display on which
+     * the main activity is showing so we must be careful to use the presentation's
+     * own {@link Context} whenever we load resources.
+     */
+    private final class DemoPresentation extends Presentation {
+
+        final PresentationContents mContents;
+
+        public DemoPresentation(Context context, Display display, PresentationContents contents) {
+            super(context, display);
+            mContents = contents;
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            // Be sure to call the super class.
+            super.onCreate(savedInstanceState);
+
+            // Get the resources for the context of the presentation.
+            // Notice that we are getting the resources from the context of the presentation.
+            Resources r = getContext().getResources();
+
+            // Inflate the layout.
+            setContentView(R.layout.presentation_content);
+
+            final Display display = getDisplay();
+            final int displayId = display.getDisplayId();
+            final int photo = mContents.photo;
+
+            // Show a caption to describe what's going on.
+            TextView text = (TextView)findViewById(R.id.text);
+            text.setText(r.getString(R.string.presentation_photo_text,
+                    photo, displayId, display.getName()));
+
+            // Show a n image for visual interest.
+            ImageView image = (ImageView)findViewById(R.id.image);
+            image.setImageDrawable(r.getDrawable(PHOTOS[photo]));
+
+            GradientDrawable drawable = new GradientDrawable();
+            drawable.setShape(GradientDrawable.RECTANGLE);
+            drawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
+
+            // Set the background to a random gradient.
+            Point p = new Point();
+            getDisplay().getSize(p);
+            drawable.setGradientRadius(Math.max(p.x, p.y) / 2);
+            drawable.setColors(mContents.colors);
+            findViewById(android.R.id.content).setBackground(drawable);
+        }
+    }
+
+    /**
+     * Information about the content we want to show in a presentation.
+     */
+    private final static class PresentationContents implements Parcelable {
+        final int photo;
+        final int[] colors;
+
+        public static final Creator<PresentationContents> CREATOR =
+                new Creator<PresentationContents>() {
+            @Override
+            public PresentationContents createFromParcel(Parcel in) {
+                return new PresentationContents(in);
+            }
+
+            @Override
+            public PresentationContents[] newArray(int size) {
+                return new PresentationContents[size];
+            }
+        };
+
+        public PresentationContents(int photo) {
+            this.photo = photo;
+            colors = new int[] {
+                    ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000,
+                    ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000 };
+        }
+
+        private PresentationContents(Parcel in) {
+            photo = in.readInt();
+            colors = new int[] { in.readInt(), in.readInt() };
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(photo);
+            dest.writeInt(colors[0]);
+            dest.writeInt(colors[1]);
+        }
+    }
+}
+//END_INCLUDE(activity)
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
new file mode 100644
index 0000000..5ba476e
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+import com.example.android.apis.graphics.CubeRenderer;
+
+import android.app.Activity;
+import android.app.MediaRouteActionProvider;
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+//BEGIN_INCLUDE(activity)
+/**
+ * <h3>Presentation Activity</h3>
+ *
+ * <p>
+ * This demonstrates how to create an activity that shows some content
+ * on a secondary display using a {@link Presentation}.
+ * </p><p>
+ * The activity uses the {@link MediaRouter} API to automatically detect when
+ * a presentation display is available and to allow the user to control the
+ * media routes using a menu item.  When a presentation display is available,
+ * we stop showing content in the main activity and instead open up a
+ * {@link Presentation} on the preferred presentation display.  When a presentation
+ * display is removed, we revert to showing content in the main activity.
+ * We also write information about displays and display-related events to
+ * the Android log which you can read using <code>adb logcat</code>.
+ * </p><p>
+ * You can try this out using an HDMI or Wifi display or by using the
+ * "Simulate secondary displays" feature in Development Settings to create a few
+ * simulated secondary displays.  Each display will appear in the list along with a
+ * checkbox to show a presentation on that display.
+ * </p><p>
+ * See also the {@link PresentationActivity} sample which
+ * uses the low-level display manager to enumerate displays and to show multiple
+ * simultaneous presentations on different displays.
+ * </p>
+ */
+public class PresentationWithMediaRouterActivity extends Activity {
+    private final String TAG = "PresentationWithMediaRouterActivity";
+
+    private MediaRouter mMediaRouter;
+    private DemoPresentation mPresentation;
+    private GLSurfaceView mSurfaceView;
+    private TextView mInfoTextView;
+    private boolean mPaused;
+
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        // Get the media router service.
+        mMediaRouter = (MediaRouter)getSystemService(Context.MEDIA_ROUTER_SERVICE);
+
+        // See assets/res/any/layout/presentation_with_media_router_activity.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.presentation_with_media_router_activity);
+
+        // Set up the surface view for visual interest.
+        mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
+        mSurfaceView.setRenderer(new CubeRenderer(false));
+
+        // Get a text view where we will show information about what's happening.
+        mInfoTextView = (TextView)findViewById(R.id.info);
+    }
+
+    @Override
+    protected void onResume() {
+        // Be sure to call the super class.
+        super.onResume();
+
+        // Listen for changes to media routes.
+        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
+
+        // Update the presentation based on the currently selected route.
+        mPaused = false;
+        updatePresentation();
+    }
+
+    @Override
+    protected void onPause() {
+        // Be sure to call the super class.
+        super.onPause();
+
+        // Stop listening for changes to media routes.
+        mMediaRouter.removeCallback(mMediaRouterCallback);
+
+        // Pause rendering.
+        mPaused = true;
+        updateContents();
+    }
+
+    @Override
+    protected void onStop() {
+        // Be sure to call the super class.
+        super.onStop();
+
+        // Dismiss the presentation when the activity is not visible.
+        if (mPresentation != null) {
+            Log.i(TAG, "Dismissing presentation because the activity is no longer visible.");
+            mPresentation.dismiss();
+            mPresentation = null;
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Be sure to call the super class.
+        super.onCreateOptionsMenu(menu);
+
+        // Inflate the menu and configure the media router action provider.
+        getMenuInflater().inflate(R.menu.presentation_with_media_router_menu, menu);
+
+        MenuItem mediaRouteMenuItem = menu.findItem(R.id.menu_media_route);
+        MediaRouteActionProvider mediaRouteActionProvider =
+                (MediaRouteActionProvider)mediaRouteMenuItem.getActionProvider();
+        mediaRouteActionProvider.setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
+
+        // Return true to show the menu.
+        return true;
+    }
+
+    private void updatePresentation() {
+        // Get the current route and its presentation display.
+        MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
+                MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
+        Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;
+
+        // Dismiss the current presentation if the display has changed.
+        if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
+            Log.i(TAG, "Dismissing presentation because the current route no longer "
+                    + "has a presentation display.");
+            mPresentation.dismiss();
+            mPresentation = null;
+        }
+
+        // Show a new presentation if needed.
+        if (mPresentation == null && presentationDisplay != null) {
+            Log.i(TAG, "Showing presentation on display: " + presentationDisplay);
+            mPresentation = new DemoPresentation(this, presentationDisplay);
+            mPresentation.setOnDismissListener(mOnDismissListener);
+            try {
+                mPresentation.show();
+            } catch (WindowManager.InvalidDisplayException ex) {
+                Log.w(TAG, "Couldn't show presentation!  Display was removed in "
+                        + "the meantime.", ex);
+                mPresentation = null;
+            }
+        }
+
+        // Update the contents playing in this activity.
+        updateContents();
+    }
+
+    private void updateContents() {
+        // Show either the content in the main activity or the content in the presentation
+        // along with some descriptive text about what is happening.
+        if (mPresentation != null) {
+            mInfoTextView.setText(getResources().getString(
+                    R.string.presentation_with_media_router_now_playing_remotely,
+                    mPresentation.getDisplay().getName()));
+            mSurfaceView.setVisibility(View.INVISIBLE);
+            mSurfaceView.onPause();
+            if (mPaused) {
+                mPresentation.getSurfaceView().onPause();
+            } else {
+                mPresentation.getSurfaceView().onResume();
+            }
+        } else {
+            mInfoTextView.setText(getResources().getString(
+                    R.string.presentation_with_media_router_now_playing_locally,
+                    getWindowManager().getDefaultDisplay().getName()));
+            mSurfaceView.setVisibility(View.VISIBLE);
+            if (mPaused) {
+                mSurfaceView.onPause();
+            } else {
+                mSurfaceView.onResume();
+            }
+        }
+    }
+
+    private final MediaRouter.SimpleCallback mMediaRouterCallback =
+            new MediaRouter.SimpleCallback() {
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
+            updatePresentation();
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
+            updatePresentation();
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
+            Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
+            updatePresentation();
+        }
+    };
+
+    /**
+     * Listens for when presentations are dismissed.
+     */
+    private final DialogInterface.OnDismissListener mOnDismissListener =
+            new DialogInterface.OnDismissListener() {
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            if (dialog == mPresentation) {
+                Log.i(TAG, "Presentation was dismissed.");
+                mPresentation = null;
+                updateContents();
+            }
+        }
+    };
+
+    /**
+     * The presentation to show on the secondary display.
+     * <p>
+     * Note that this display may have different metrics from the display on which
+     * the main activity is showing so we must be careful to use the presentation's
+     * own {@link Context} whenever we load resources.
+     * </p>
+     */
+    private final static class DemoPresentation extends Presentation {
+        private GLSurfaceView mSurfaceView;
+
+        public DemoPresentation(Context context, Display display) {
+            super(context, display);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            // Be sure to call the super class.
+            super.onCreate(savedInstanceState);
+
+            // Get the resources for the context of the presentation.
+            // Notice that we are getting the resources from the context of the presentation.
+            Resources r = getContext().getResources();
+
+            // Inflate the layout.
+            setContentView(R.layout.presentation_with_media_router_content);
+
+            // Set up the surface view for visual interest.
+            mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
+            mSurfaceView.setRenderer(new CubeRenderer(false));
+        }
+
+        public GLSurfaceView getSurfaceView() {
+            return mSurfaceView;
+        }
+    }
+}
+//END_INCLUDE(activity)
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java
new file mode 100644
index 0000000..fe871aa
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureDialogActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+
+/**
+ * <h3>Secure Dialog Activity</h3>
+ *
+ * <p>
+ * This activity demonstrates how to create a dialog whose window is backed by
+ * a secure surface using {@link WindowManager.LayoutParams#FLAG_SECURE}.
+ * Because the surface is secure, its contents cannot be captured in screenshots
+ * and will not be visible on non-secure displays even when mirrored.
+ * </p><p>
+ * Here are a few things you can do to experiment with secure surfaces and
+ * observe their behavior.
+ * <ul>
+ * <li>Try taking a screenshot.  Either the system will prevent you from taking
+ * a screenshot altogether or the screenshot should not contain the contents
+ * of the secure surface.
+ * <li>Try mirroring the secure surface onto a non-secure display such as an
+ * "Overlay Display" created using the "Simulate secondary displays" option in
+ * the "Developer options" section of the Settings application.  The non-secure
+ * secondary display should not show the contents of the secure surface.
+ * <li>Try mirroring the secure surface onto a secure display such as an
+ * HDMI display with HDCP enabled.  The contents of the secure surface should appear
+ * on the display.
+ * </ul>
+ * </p>
+ */
+public class SecureDialogActivity extends Activity
+        implements View.OnClickListener {
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        // See assets/res/any/layout/secure_dialog_activity.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.secure_dialog_activity);
+
+        // Handle click events on the button to show the dialog.
+        Button button = (Button)findViewById(R.id.show);
+        button.setOnClickListener(this);
+    }
+
+    /**
+     * Called when the button to show the dialog is clicked.
+     */
+    @Override
+    public void onClick(View v) {
+        // Create a dialog.
+        AlertDialog dialog = new AlertDialog.Builder(this)
+                .setPositiveButton(android.R.string.ok, null)
+                .setMessage(R.string.secure_dialog_dialog_text)
+                .create();
+
+        // Make the dialog secure.  This must be done at the time the dialog is
+        // created.  It cannot be changed after the dialog has been shown.
+        dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+                WindowManager.LayoutParams.FLAG_SECURE);
+
+        // Show the dialog.
+        dialog.show();
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java
new file mode 100644
index 0000000..978c70c
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureSurfaceViewActivity.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+import com.example.android.apis.graphics.CubeRenderer;
+
+import android.app.Activity;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+/**
+ * <h3>Secure Window Activity</h3>
+ *
+ * <p>
+ * This activity demonstrates how to create a {@link SurfaceView} backed by
+ * a secure surface using {@link SurfaceView#setSecure}.
+ * Because the surface is secure, its contents cannot be captured in screenshots
+ * and will not be visible on non-secure displays even when mirrored.
+ * </p><p>
+ * Here are a few things you can do to experiment with secure surfaces and
+ * observe their behavior.
+ * <ul>
+ * <li>Try taking a screenshot.  Either the system will prevent you from taking
+ * a screenshot altogether or the screenshot should not contain the contents
+ * of the secure surface.
+ * <li>Try mirroring the secure surface onto a non-secure display such as an
+ * "Overlay Display" created using the "Simulate secondary displays" option in
+ * the "Developer options" section of the Settings application.  The non-secure
+ * secondary display should not show the contents of the secure surface.
+ * <li>Try mirroring the secure surface onto a secure display such as an
+ * HDMI display with HDCP enabled.  The contents of the secure surface should appear
+ * on the display.
+ * </ul>
+ * </p>
+ */
+public class SecureSurfaceViewActivity extends Activity {
+    private GLSurfaceView mSurfaceView;
+
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        // See assets/res/any/layout/secure_surface_view_activity.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.secure_surface_view_activity);
+
+        // Set up the surface view.
+        // We use a GLSurfaceView in this demonstration but ordinary
+        // SurfaceViews also support the same secure surface functionality.
+        mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
+        mSurfaceView.setRenderer(new CubeRenderer(false));
+
+        // Make the surface view secure.  This must be done at the time the surface view
+        // is created before the surface view's containing window is attached to
+        // the window manager which happens after onCreate returns.
+        // It cannot be changed later.
+        mSurfaceView.setSecure(true);
+    }
+
+    @Override
+    protected void onResume() {
+        // Be sure to call the super class.
+        super.onResume();
+
+        // Resume rendering.
+        mSurfaceView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Be sure to call the super class.
+        super.onPause();
+
+        // Pause rendering.
+        mSurfaceView.onPause();
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java
new file mode 100644
index 0000000..4b5e8b6
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/SecureWindowActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * <h3>Secure Window Activity</h3>
+ *
+ * <p>
+ * This activity demonstrates how to create an activity whose window is backed by
+ * a secure surface using {@link WindowManager.LayoutParams#FLAG_SECURE}.
+ * Because the surface is secure, its contents cannot be captured in screenshots
+ * and will not be visible on non-secure displays even when mirrored.
+ * </p><p>
+ * Here are a few things you can do to experiment with secure surfaces and
+ * observe their behavior.
+ * <ul>
+ * <li>Try taking a screenshot.  Either the system will prevent you from taking
+ * a screenshot altogether or the screenshot should not contain the contents
+ * of the secure surface.
+ * <li>Try mirroring the secure surface onto a non-secure display such as an
+ * "Overlay Display" created using the "Simulate secondary displays" option in
+ * the "Developer options" section of the Settings application.  The non-secure
+ * secondary display should not show the contents of the secure surface.
+ * <li>Try mirroring the secure surface onto a secure display such as an
+ * HDMI display with HDCP enabled.  The contents of the secure surface should appear
+ * on the display.
+ * </ul>
+ * </p>
+ */
+public class SecureWindowActivity extends Activity {
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+
+        // See assets/res/any/layout/secure_window_activity.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.secure_window_activity);
+
+        // Make the window secure.  This must be done at the time the activity
+        // is created.  It cannot be changed later.
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
+                WindowManager.LayoutParams.FLAG_SECURE);
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java b/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java
index ac0ae27..b30808c 100644
--- a/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.java
@@ -25,7 +25,7 @@
  * Render a pair of tumbling cubes.
  */
 
-class CubeRenderer implements GLSurfaceView.Renderer {
+public class CubeRenderer implements GLSurfaceView.Renderer {
     public CubeRenderer(boolean useTranslucentBackground) {
         mTranslucentBackground = useTranslucentBackground;
         mCube = new Cube();
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java
deleted file mode 100644
index 70de1ac..0000000
--- a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java
+++ /dev/null
@@ -1,113 +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 com.example.android.apis.view;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.View;
-
-import android.widget.*;
-
-import static android.text.InputType.*;
-import static android.widget.GridLayout.*;
-
-/**
- * A simple form, showing use of the GridLayout API.
- */
-public class GridLayout0 extends Activity {
-
-    public static View create(Context context) {
-        GridLayout p = new GridLayout(context);
-        p.setUseDefaultMargins(true);
-        p.setAlignmentMode(ALIGN_BOUNDS);
-        p.setRowOrderPreserved(false);
-
-        Spec row1 = spec(0);
-        Spec row2 = spec(1);
-        Spec row3 = spec(2, BASELINE);
-        Spec row4 = spec(3, BASELINE);
-        Spec row5 = spec(2, 3, FILL); // allow the last two rows to overlap the middle two
-        Spec row6 = spec(5);
-        Spec row7 = spec(6);
-
-        Spec col1a = spec(0, 4, CENTER);
-        Spec col1b = spec(0, 4, LEFT);
-        Spec col1c = spec(0, RIGHT);
-        Spec col2 = spec(1, LEFT);
-        Spec col3 = spec(2, FILL);
-        Spec col4a = spec(3);
-        Spec col4b = spec(3, FILL);
-
-        {
-            TextView c = new TextView(context);
-            c.setTextSize(32);
-            c.setText("Email setup");
-            p.addView(c, new LayoutParams(row1, col1a));
-        }
-        {
-            TextView c = new TextView(context);
-            c.setTextSize(16);
-            c.setText("You can configure email in just a few steps:");
-            p.addView(c, new LayoutParams(row2, col1b));
-        }
-        {
-            TextView c = new TextView(context);
-            c.setText("Email address:");
-            p.addView(c, new LayoutParams(row3, col1c));
-        }
-        {
-            EditText c = new EditText(context);
-            c.setEms(10);
-            c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
-            p.addView(c, new LayoutParams(row3, col2));
-        }
-        {
-            TextView c = new TextView(context);
-            c.setText("Password:");
-            p.addView(c, new LayoutParams(row4, col1c));
-        }
-        {
-            TextView c = new EditText(context);
-            c.setEms(8);
-            c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD);
-            p.addView(c, new LayoutParams(row4, col2));
-        }
-        {
-            Space c = new Space(context);
-            p.addView(c, new LayoutParams(row5, col3));
-        }
-        {
-            Button c = new Button(context);
-            c.setText("Manual setup");
-            p.addView(c, new LayoutParams(row6, col4a));
-        }
-        {
-            Button c = new Button(context);
-            c.setText("Next");
-            p.addView(c, new LayoutParams(row7, col4b));
-        }
-
-        return p;
-    }
-
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(create(this));
-    }
-
-}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java
index f4318e4..3221fa0 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout1.java
@@ -16,13 +16,13 @@
 
 package com.example.android.apis.view;
 
-import com.example.android.apis.R;
-
 import android.app.Activity;
 import android.os.Bundle;
+import com.example.android.apis.R;
 
 /**
- * A simple form, showing use of the GridLayout API from XML.
+ * Demonstrates using GridLayout to build the same "Simple Form" as in the
+ * LinearLayout and RelativeLayout demos.
  */
 public class GridLayout1 extends Activity {
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java
new file mode 100644
index 0000000..2ad09f4
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout2.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.view;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A form, showing use of the GridLayout API from XML.
+ */
+public class GridLayout2 extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.grid_layout_2);
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/GridLayout3.java b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout3.java
new file mode 100644
index 0000000..48a5546
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/GridLayout3.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.example.android.apis.view;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.View;
+
+import android.widget.*;
+
+import static android.text.InputType.*;
+import static android.widget.GridLayout.*;
+import static android.widget.GridLayout.LayoutParams;
+
+/**
+ * A form, showing use of the GridLayout API. Here we demonstrate use of the row/column order
+ * preserved property which allows rows and or columns to pass over each other when needed.
+ * The two buttons in the bottom right corner need to be separated from the other UI elements.
+ * This can either be done by separating rows or separating columns - but we don't need
+ * to do both and may only have enough space to do one or the other.
+ */
+public class GridLayout3 extends Activity {
+    public static View create(Context context) {
+        GridLayout p = new GridLayout(context);
+        p.setUseDefaultMargins(true);
+        p.setAlignmentMode(ALIGN_BOUNDS);
+        Configuration configuration = context.getResources().getConfiguration();
+        if ((configuration.orientation == Configuration.ORIENTATION_PORTRAIT)) {
+            p.setColumnOrderPreserved(false);
+        } else {
+            p.setRowOrderPreserved(false);
+        }
+
+        Spec titleRow              = spec(0);
+        Spec introRow              = spec(1);
+        Spec emailRow              = spec(2, BASELINE);
+        Spec passwordRow           = spec(3, BASELINE);
+        Spec button1Row            = spec(5);
+        Spec button2Row            = spec(6);
+
+        Spec centerInAllColumns    = spec(0, 4, CENTER);
+        Spec leftAlignInAllColumns = spec(0, 4, LEFT);
+        Spec labelColumn           = spec(0, RIGHT);
+        Spec fieldColumn           = spec(1, LEFT);
+        Spec defineLastColumn      = spec(3);
+        Spec fillLastColumn        = spec(3, FILL);
+
+        {
+            TextView c = new TextView(context);
+            c.setTextSize(32);
+            c.setText("Email setup");
+            p.addView(c, new LayoutParams(titleRow, centerInAllColumns));
+        }
+        {
+            TextView c = new TextView(context);
+            c.setTextSize(16);
+            c.setText("You can configure email in a few simple steps:");
+            p.addView(c, new LayoutParams(introRow, leftAlignInAllColumns));
+        }
+        {
+            TextView c = new TextView(context);
+            c.setText("Email address:");
+            p.addView(c, new LayoutParams(emailRow, labelColumn));
+        }
+        {
+            EditText c = new EditText(context);
+            c.setEms(10);
+            c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+            p.addView(c, new LayoutParams(emailRow, fieldColumn));
+        }
+        {
+            TextView c = new TextView(context);
+            c.setText("Password:");
+            p.addView(c, new LayoutParams(passwordRow, labelColumn));
+        }
+        {
+            TextView c = new EditText(context);
+            c.setEms(8);
+            c.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD);
+            p.addView(c, new LayoutParams(passwordRow, fieldColumn));
+        }
+        {
+            Button c = new Button(context);
+            c.setText("Manual setup");
+            p.addView(c, new LayoutParams(button1Row, defineLastColumn));
+        }
+        {
+            Button c = new Button(context);
+            c.setText("Next");
+            p.addView(c, new LayoutParams(button2Row, fillLastColumn));
+        }
+
+        return p;
+    }
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(create(this));
+    }
+
+}
\ No newline at end of file
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java b/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java
new file mode 100644
index 0000000..f2b85ce
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/view/TextClockDemo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.view;
+
+// Need the following import to get access to the app resources, since this
+// class is in a sub-package.
+import android.text.Html;
+import android.widget.TextClock;
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Variants of {@link TextClock}.
+ */
+public class TextClockDemo extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.textclock);
+    }
+}
diff --git a/samples/RenderScript/HelloCompute/Android.mk b/samples/RenderScript/HelloCompute/Android.mk
index e19f351..27798c4 100644
--- a/samples/RenderScript/HelloCompute/Android.mk
+++ b/samples/RenderScript/HelloCompute/Android.mk
@@ -23,5 +23,6 @@
                    $(call all-renderscript-files-under, src)
 
 LOCAL_PACKAGE_NAME := RsHelloCompute
+LOCAL_SDK_VERSION := 14
 
 include $(BUILD_PACKAGE)
diff --git a/samples/Support13Demos/AndroidManifest.xml b/samples/Support13Demos/AndroidManifest.xml
index bc32d09..dfa4e64 100644
--- a/samples/Support13Demos/AndroidManifest.xml
+++ b/samples/Support13Demos/AndroidManifest.xml
@@ -47,6 +47,22 @@
 
         <!-- Fragment Support Samples -->
 
+        <activity android:name=".app.FragmentNestingPagerSupport"
+                android:label="@string/fragment_nesting_pager_support">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentNestingStatePagerSupport"
+                android:label="@string/fragment_nesting_state_pager_support">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.FragmentPagerSupport"
                 android:label="@string/fragment_pager_support">
             <intent-filter>
diff --git a/samples/Support13Demos/res/layout/counting.xml b/samples/Support13Demos/res/layout/counting.xml
new file mode 100644
index 0000000..7d37c37
--- /dev/null
+++ b/samples/Support13Demos/res/layout/counting.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Demonstrates basic application screen.
+     See corresponding Java code com.android.sdk.app.HelloWorld.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:drawable/gallery_thumb">
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent" android:layout_height="wrap_content"
+        android:gravity="center_vertical|center_horizontal"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/hello_world"/>
+    <CheckBox android:id="@+id/menu1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:checked="true"
+        android:text="@string/retained">
+    </CheckBox>
+</LinearLayout>
diff --git a/samples/Support13Demos/res/layout/hello_world.xml b/samples/Support13Demos/res/layout/hello_world.xml
deleted file mode 100644
index 433ea9b..0000000
--- a/samples/Support13Demos/res/layout/hello_world.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- Demonstrates basic application screen.
-     See corresponding Java code com.android.sdk.app.HelloWorld.java. -->
-
-<!-- This screen consists of a single text field that
-     displays our "Hello, World!" text. -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
-    android:layout_width="match_parent" android:layout_height="match_parent"
-    android:gravity="center_vertical|center_horizontal"
-    android:textAppearance="?android:attr/textAppearanceMedium"
-    android:text="@string/hello_world"/>
diff --git a/samples/Support13Demos/res/values/strings.xml b/samples/Support13Demos/res/values/strings.xml
index 2fd12d4..92316d0 100644
--- a/samples/Support13Demos/res/values/strings.xml
+++ b/samples/Support13Demos/res/values/strings.xml
@@ -18,11 +18,17 @@
     <string name="activity_sample_code">Support v13 Demos</string>
 
     <string name="hello_world"><b>Hello, <i>World!</i></b></string>
+    <string name="retained">Retained state</string>
+
     <string name="alert_dialog_two_buttons_title">
         Lorem ipsum dolor sit aie consectetur adipiscing\nPlloaso mako nuto
         siwuf cakso dodtos anr koop.
     </string>
 
+    <string name="fragment_nesting_pager_support">Fragment/Nesting Pager</string>
+
+    <string name="fragment_nesting_state_pager_support">Fragment/Nesting State Pager</string>
+
     <string name="fragment_pager_support">Fragment/Pager</string>
     <string name="first">First</string>
     <string name="last">Last</string>
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java
index 8672ed2..d52955b 100644
--- a/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java
@@ -58,10 +58,9 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        View v = inflater.inflate(R.layout.hello_world, container, false);
+        View v = inflater.inflate(R.layout.counting, container, false);
         View tv = v.findViewById(R.id.text);
         ((TextView)tv).setText("Fragment #" + mNum);
-        tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
         return v;
     }
 }
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingPagerSupport.java b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingPagerSupport.java
new file mode 100644
index 0000000..0a88a05
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingPagerSupport.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv13.app;
+
+import java.util.ArrayList;
+
+import com.example.android.supportv13.R;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+
+//BEGIN_INCLUDE(complete)
+public class FragmentNestingPagerSupport extends Activity {
+    ViewPager mViewPager;
+    TabsAdapter mTabsAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mViewPager = new ViewPager(this);
+        mViewPager.setId(R.id.pager);
+        setContentView(mViewPager);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+
+        mTabsAdapter = new TabsAdapter(this, mViewPager);
+        mTabsAdapter.addTab(bar.newTab().setText("Simple"),
+                CountingFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("List"),
+                FragmentPagerSupport.ArrayListFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("Cursor"),
+                CursorFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("Tabs"),
+                FragmentTabsFragment.class, null);
+
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    /**
+     * This is a helper class that implements the management of tabs and all
+     * details of connecting a ViewPager with associated TabHost.  It relies on a
+     * trick.  Normally a tab host has a simple API for supplying a View or
+     * Intent that each tab will show.  This is not sufficient for switching
+     * between pages.  So instead we make the content part of the tab host
+     * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
+     * view to show as the tab content.  It listens to changes in tabs, and takes
+     * care of switch to the correct paged in the ViewPager whenever the selected
+     * tab changes.
+     */
+    public static class TabsAdapter extends FragmentPagerAdapter
+            implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
+        private final Context mContext;
+        private final ActionBar mActionBar;
+        private final ViewPager mViewPager;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+        static final class TabInfo {
+            private final Class<?> clss;
+            private final Bundle args;
+
+            TabInfo(Class<?> _class, Bundle _args) {
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        public TabsAdapter(Activity activity, ViewPager pager) {
+            super(activity.getFragmentManager());
+            mContext = activity;
+            mActionBar = activity.getActionBar();
+            mViewPager = pager;
+            mViewPager.setAdapter(this);
+            mViewPager.setOnPageChangeListener(this);
+        }
+
+        public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
+            TabInfo info = new TabInfo(clss, args);
+            tab.setTag(info);
+            tab.setTabListener(this);
+            mTabs.add(info);
+            mActionBar.addTab(tab);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mTabs.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            TabInfo info = mTabs.get(position);
+            return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            mActionBar.setSelectedNavigationItem(position);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+        }
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            Object tag = tab.getTag();
+            for (int i=0; i<mTabs.size(); i++) {
+                if (mTabs.get(i) == tag) {
+                    mViewPager.setCurrentItem(i);
+                }
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+        }
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java
new file mode 100644
index 0000000..5863852
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv13.app;
+
+import java.util.ArrayList;
+
+import com.example.android.supportv13.R;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v13.app.FragmentStatePagerAdapter;
+import android.support.v4.view.ViewPager;
+
+//BEGIN_INCLUDE(complete)
+public class FragmentNestingStatePagerSupport extends Activity {
+    ViewPager mViewPager;
+    TabsAdapter mTabsAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mViewPager = new ViewPager(this);
+        mViewPager.setId(R.id.pager);
+        setContentView(mViewPager);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+
+        mTabsAdapter = new TabsAdapter(this, mViewPager);
+        mTabsAdapter.addTab(bar.newTab().setText("Simple"),
+                CountingFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("List"),
+                FragmentPagerSupport.ArrayListFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("Cursor"),
+                CursorFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("Tabs"),
+                FragmentTabsFragment.class, null);
+
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    /**
+     * This is a helper class that implements the management of tabs and all
+     * details of connecting a ViewPager with associated TabHost.  It relies on a
+     * trick.  Normally a tab host has a simple API for supplying a View or
+     * Intent that each tab will show.  This is not sufficient for switching
+     * between pages.  So instead we make the content part of the tab host
+     * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
+     * view to show as the tab content.  It listens to changes in tabs, and takes
+     * care of switch to the correct paged in the ViewPager whenever the selected
+     * tab changes.
+     */
+    public static class TabsAdapter extends FragmentStatePagerAdapter
+            implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
+        private final Context mContext;
+        private final ActionBar mActionBar;
+        private final ViewPager mViewPager;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+        static final class TabInfo {
+            private final Class<?> clss;
+            private final Bundle args;
+
+            TabInfo(Class<?> _class, Bundle _args) {
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        public TabsAdapter(Activity activity, ViewPager pager) {
+            super(activity.getFragmentManager());
+            mContext = activity;
+            mActionBar = activity.getActionBar();
+            mViewPager = pager;
+            mViewPager.setAdapter(this);
+            mViewPager.setOnPageChangeListener(this);
+        }
+
+        public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
+            TabInfo info = new TabInfo(clss, args);
+            tab.setTag(info);
+            tab.setTabListener(this);
+            mTabs.add(info);
+            mActionBar.addTab(tab);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mTabs.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            TabInfo info = mTabs.get(position);
+            return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            mActionBar.setSelectedNavigationItem(position);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+        }
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            Object tag = tab.getTag();
+            for (int i=0; i<mTabs.size(); i++) {
+                if (mTabs.get(i) == tag) {
+                    mViewPager.setCurrentItem(i);
+                }
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+        }
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentTabsFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentTabsFragment.java
new file mode 100644
index 0000000..4415851
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentTabsFragment.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv13.app;
+
+import com.example.android.supportv13.R;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v13.app.FragmentTabHost;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class FragmentTabsFragment extends Fragment {
+    private FragmentTabHost mTabHost;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mTabHost = new FragmentTabHost(getActivity());
+        mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.pager);
+
+        mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
+                CountingFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("array").setIndicator("Array"),
+                FragmentPagerSupport.ArrayListFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("cursor").setIndicator("Cursor"),
+                CursorFragment.class, null);
+
+        return mTabHost;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mTabHost = null;
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/Support4Demos/AndroidManifest.xml b/samples/Support4Demos/AndroidManifest.xml
index 1aa2107..b747876 100644
--- a/samples/Support4Demos/AndroidManifest.xml
+++ b/samples/Support4Demos/AndroidManifest.xml
@@ -134,6 +134,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.FragmentNestingTabsSupport"
+                android:label="@string/fragment_nesting_tabs_support">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+        
         <activity android:name=".app.FragmentRetainInstanceSupport"
                 android:label="@string/fragment_retain_instance_support">
             <intent-filter>
diff --git a/samples/Support4Demos/res/layout/fragment_stack.xml b/samples/Support4Demos/res/layout/fragment_stack.xml
index 0f0951f..3825516 100644
--- a/samples/Support4Demos/res/layout/fragment_stack.xml
+++ b/samples/Support4Demos/res/layout/fragment_stack.xml
@@ -39,8 +39,11 @@
         <Button android:id="@+id/new_fragment"
             android:layout_width="wrap_content" android:layout_height="wrap_content"
             android:text="@string/new_fragment">
-            <requestFocus />
         </Button>
-    </LinearLayout>
+        <Button android:id="@+id/delete_fragment"
+            android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:text="@string/delete_fragment">
+        </Button>
+        </LinearLayout>
 
 </LinearLayout>
diff --git a/samples/Support4Demos/res/layout/fragment_tabs.xml b/samples/Support4Demos/res/layout/fragment_tabs.xml
index 18297b5..e443391 100644
--- a/samples/Support4Demos/res/layout/fragment_tabs.xml
+++ b/samples/Support4Demos/res/layout/fragment_tabs.xml
@@ -19,7 +19,7 @@
 -->
 
 <!-- BEGIN_INCLUDE(complete) -->
-<TabHost
+<android.support.v4.app.FragmentTabHost
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@android:id/tabhost"
     android:layout_width="match_parent"
@@ -44,11 +44,11 @@
             android:layout_weight="0"/>
 
         <FrameLayout
-            android:id="@+android:id/realtabcontent"
+            android:id="@+id/realtabcontent"
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"/>
 
     </LinearLayout>
-</TabHost>
+</android.support.v4.app.FragmentTabHost>
 <!-- END_INCLUDE(complete) -->
diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml
index e17974d..35cbb75 100644
--- a/samples/Support4Demos/res/values/strings.xml
+++ b/samples/Support4Demos/res/values/strings.xml
@@ -69,6 +69,8 @@
     <string name="fragment1menu">Show fragment 1 menu</string>
     <string name="fragment2menu">Show fragment 2 menu</string>
 
+    <string name="fragment_nesting_tabs_support">Fragment/Nesting Tabs</string>
+
     <string name="fragment_retain_instance_support">Fragment/Retain Instance</string>
     <string name="fragment_retain_instance_msg">Current progress of retained fragment;
     restarts if fragment is re-created.</string>
@@ -78,8 +80,9 @@
 
     <string name="fragment_stack_support">Fragment/Stack</string>
     <string name="home">Go home</string>
-    <string name="new_fragment">New fragment</string>
-
+    <string name="new_fragment">Add new</string>
+    <string name="delete_fragment">Pop top</string>
+    
     <string name="fragment_tabs">Fragment/Tabs</string>
 
     <string name="fragment_tabs_pager">Fragment/Tabs and Pager</string>
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentLayoutSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentLayoutSupport.java
index 25ec4a3..8be83a6 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentLayoutSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentLayoutSupport.java
@@ -153,7 +153,11 @@
                     // Execute a transaction, replacing any existing fragment
                     // with this one inside the frame.
                     FragmentTransaction ft = getFragmentManager().beginTransaction();
-                    ft.replace(R.id.details, details);
+                    if (index == 0) {
+                        ft.replace(R.id.details, details);
+                    } else {
+                        ft.replace(R.id.a_item, details);
+                    }
                     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                     ft.commit();
                 }
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentMenuFragmentSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentMenuFragmentSupport.java
new file mode 100644
index 0000000..fb65a2b
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentMenuFragmentSupport.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.supportv4.app;
+
+import com.example.android.supportv4.R;
+
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+
+/**
+ * Demonstrates how fragments can participate in the options menu.
+ */
+public class FragmentMenuFragmentSupport extends Fragment {
+    Fragment mFragment1;
+    Fragment mFragment2;
+    CheckBox mCheckBox1;
+    CheckBox mCheckBox2;
+
+    // Update fragment visibility when check boxes are changed.
+    final OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            updateFragmentVisibility();
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_menu, container, false);
+
+        // Make sure the two menu fragments are created.
+        FragmentManager fm = getChildFragmentManager();
+        FragmentTransaction ft = fm.beginTransaction();
+        mFragment1 = fm.findFragmentByTag("f1");
+        if (mFragment1 == null) {
+            mFragment1 = new FragmentMenuSupport.MenuFragment();
+            ft.add(mFragment1, "f1");
+        }
+        mFragment2 = fm.findFragmentByTag("f2");
+        if (mFragment2 == null) {
+            mFragment2 = new FragmentMenuSupport.Menu2Fragment();
+            ft.add(mFragment2, "f2");
+        }
+        ft.commit();
+        
+        // Watch check box clicks.
+        mCheckBox1 = (CheckBox)v.findViewById(R.id.menu1);
+        mCheckBox1.setOnClickListener(mClickListener);
+        mCheckBox2 = (CheckBox)v.findViewById(R.id.menu2);
+        mCheckBox2.setOnClickListener(mClickListener);
+        
+        // Make sure fragments start out with correct visibility.
+        updateFragmentVisibility();
+
+        return v;
+    }
+
+    @Override
+    public void onViewStateRestored(Bundle savedInstanceState) {
+        super.onViewStateRestored(savedInstanceState);
+        // Make sure fragments are updated after check box view state is restored.
+        updateFragmentVisibility();
+    }
+
+    // Update fragment visibility based on current check box state.
+    void updateFragmentVisibility() {
+        FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+        if (mCheckBox1.isChecked()) ft.show(mFragment1);
+        else ft.hide(mFragment1);
+        if (mCheckBox2.isChecked()) ft.show(mFragment2);
+        else ft.hide(mFragment2);
+        ft.commit();
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentNestingTabsSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
new file mode 100644
index 0000000..dfdbc21
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
@@ -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.
+ */
+package com.example.android.supportv4.app;
+
+//BEGIN_INCLUDE(complete)
+import com.example.android.supportv4.R;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTabHost;
+
+public class FragmentNestingTabsSupport extends FragmentActivity {
+    private FragmentTabHost mTabHost;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mTabHost = new FragmentTabHost(this);
+        setContentView(mTabHost);
+        mTabHost.setup(this, getSupportFragmentManager(), R.id.fragment1);
+
+        mTabHost.addTab(mTabHost.newTabSpec("menus").setIndicator("Menus"),
+                FragmentMenuFragmentSupport.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
+                LoaderCursorSupport.CursorLoaderListFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("stack").setIndicator("Stack"),
+                FragmentStackFragmentSupport.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("tabs").setIndicator("Tabs"),
+                FragmentTabsFragmentSupport.class, null);
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackFragmentSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackFragmentSupport.java
new file mode 100644
index 0000000..d2eb29a
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackFragmentSupport.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.supportv4.app;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.example.android.supportv4.R;
+
+public class FragmentStackFragmentSupport extends Fragment {
+    int mStackLevel = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            // Do first time initialization -- add initial fragment.
+            Fragment newFragment = FragmentStackSupport.CountingFragment.newInstance(mStackLevel);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+            ft.add(R.id.simple_fragment, newFragment).commit();
+        } else {
+            mStackLevel = savedInstanceState.getInt("level");
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.fragment_stack, container, false);
+
+        // Watch for button clicks.
+        Button button = (Button)v.findViewById(R.id.new_fragment);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                addFragmentToStack();
+            }
+        });
+        button = (Button)v.findViewById(R.id.delete_fragment);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                getChildFragmentManager().popBackStack();
+            }
+        });
+        button = (Button)v.findViewById(R.id.home);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // If there is a back stack, pop it all.
+                FragmentManager fm = getChildFragmentManager();
+                if (fm.getBackStackEntryCount() > 0) {
+                    fm.popBackStack(fm.getBackStackEntryAt(0).getId(),
+                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                }
+            }
+        });
+
+        return v;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("level", mStackLevel);
+    }
+
+    void addFragmentToStack() {
+        mStackLevel++;
+
+        // Instantiate a new fragment.
+        Fragment newFragment = FragmentStackSupport.CountingFragment.newInstance(mStackLevel);
+
+        // Add the fragment to the activity, pushing this transaction
+        // on to the back stack.
+        FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+        ft.replace(R.id.simple_fragment, newFragment);
+        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        ft.addToBackStack(null);
+        ft.commit();
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
index 64b21c5..7a7e2fc 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
@@ -16,156 +16,35 @@
 package com.example.android.supportv4.app;
 
 //BEGIN_INCLUDE(complete)
-import java.util.HashMap;
-
 import com.example.android.supportv4.R;
 
-import android.content.Context;
 import android.os.Bundle;
-import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentTransaction;
-import android.view.View;
-import android.widget.TabHost;
+import android.support.v4.app.FragmentTabHost;
 
 /**
  * This demonstrates how you can implement switching between the tabs of a
- * TabHost through fragments.  It uses a trick (see the code below) to allow
- * the tabs to switch between fragments instead of simple views.
+ * TabHost through fragments, using FragmentTabHost.
  */
 public class FragmentTabs extends FragmentActivity {
-    TabHost mTabHost;
-    TabManager mTabManager;
+    private FragmentTabHost mTabHost;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.fragment_tabs);
-        mTabHost = (TabHost)findViewById(android.R.id.tabhost);
-        mTabHost.setup();
+        mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
+        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
 
-        mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
-
-        mTabManager.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
+        mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
                 FragmentStackSupport.CountingFragment.class, null);
-        mTabManager.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
+        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
                 LoaderCursorSupport.CursorLoaderListFragment.class, null);
-        mTabManager.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
+        mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
                 LoaderCustomSupport.AppListFragment.class, null);
-        mTabManager.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
+        mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
                 LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
-
-        if (savedInstanceState != null) {
-            mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
-        }
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putString("tab", mTabHost.getCurrentTabTag());
-    }
-
-    /**
-     * This is a helper class that implements a generic mechanism for
-     * associating fragments with the tabs in a tab host.  It relies on a
-     * trick.  Normally a tab host has a simple API for supplying a View or
-     * Intent that each tab will show.  This is not sufficient for switching
-     * between fragments.  So instead we make the content part of the tab host
-     * 0dp high (it is not shown) and the TabManager supplies its own dummy
-     * view to show as the tab content.  It listens to changes in tabs, and takes
-     * care of switch to the correct fragment shown in a separate content area
-     * whenever the selected tab changes.
-     */
-    public static class TabManager implements TabHost.OnTabChangeListener {
-        private final FragmentActivity mActivity;
-        private final TabHost mTabHost;
-        private final int mContainerId;
-        private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
-        TabInfo mLastTab;
-
-        static final class TabInfo {
-            private final String tag;
-            private final Class<?> clss;
-            private final Bundle args;
-            private Fragment fragment;
-
-            TabInfo(String _tag, Class<?> _class, Bundle _args) {
-                tag = _tag;
-                clss = _class;
-                args = _args;
-            }
-        }
-
-        static class DummyTabFactory implements TabHost.TabContentFactory {
-            private final Context mContext;
-
-            public DummyTabFactory(Context context) {
-                mContext = context;
-            }
-
-            @Override
-            public View createTabContent(String tag) {
-                View v = new View(mContext);
-                v.setMinimumWidth(0);
-                v.setMinimumHeight(0);
-                return v;
-            }
-        }
-
-        public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
-            mActivity = activity;
-            mTabHost = tabHost;
-            mContainerId = containerId;
-            mTabHost.setOnTabChangedListener(this);
-        }
-
-        public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
-            tabSpec.setContent(new DummyTabFactory(mActivity));
-            String tag = tabSpec.getTag();
-
-            TabInfo info = new TabInfo(tag, clss, args);
-
-            // Check to see if we already have a fragment for this tab, probably
-            // from a previously saved state.  If so, deactivate it, because our
-            // initial state is that a tab isn't shown.
-            info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
-            if (info.fragment != null && !info.fragment.isDetached()) {
-                FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
-                ft.detach(info.fragment);
-                ft.commit();
-            }
-
-            mTabs.put(tag, info);
-            mTabHost.addTab(tabSpec);
-        }
-
-        @Override
-        public void onTabChanged(String tabId) {
-            TabInfo newTab = mTabs.get(tabId);
-            if (mLastTab != newTab) {
-                FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
-                if (mLastTab != null) {
-                    if (mLastTab.fragment != null) {
-                        ft.detach(mLastTab.fragment);
-                    }
-                }
-                if (newTab != null) {
-                    if (newTab.fragment == null) {
-                        newTab.fragment = Fragment.instantiate(mActivity,
-                                newTab.clss.getName(), newTab.args);
-                        ft.add(mContainerId, newTab.fragment, newTab.tag);
-                    } else {
-                        ft.attach(newTab.fragment);
-                    }
-                }
-
-                mLastTab = newTab;
-                ft.commit();
-                mActivity.getSupportFragmentManager().executePendingTransactions();
-            }
-        }
     }
 }
 //END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
new file mode 100644
index 0000000..68f06ef
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.supportv4.app;
+
+//BEGIN_INCLUDE(complete)
+import com.example.android.supportv4.R;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTabHost;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class FragmentTabsFragmentSupport extends Fragment {
+    private FragmentTabHost mTabHost;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mTabHost = new FragmentTabHost(getActivity());
+        mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1);
+
+        mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
+                FragmentStackSupport.CountingFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
+                LoaderCursorSupport.CursorLoaderListFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
+                LoaderCustomSupport.AppListFragment.class, null);
+        mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
+                LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
+
+        return mTabHost;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mTabHost = null;
+    }
+}
+//END_INCLUDE(complete)
diff --git a/samples/UiAutomator/Android.mk b/samples/UiAutomator/Android.mk
new file mode 100644
index 0000000..54191fa
--- /dev/null
+++ b/samples/UiAutomator/Android.mk
@@ -0,0 +1,30 @@
+#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.
+
+local_target_dir := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := uiautomator.samples
+
+LOCAL_JAVA_LIBRARIES := uiautomator.core
+
+LOCAL_MODULE_PATH := $(local_target_dir)
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/samples/UiAutomator/README b/samples/UiAutomator/README
new file mode 100644
index 0000000..d9c0809
--- /dev/null
+++ b/samples/UiAutomator/README
@@ -0,0 +1,31 @@
+----------- LaunchSettings Demo -----------
+
+  This demos how we read content-description to properly open the
+  All Apps view and select and application to launch. Then we will
+  use the package name to verify that the current window is actually
+  from the expected package
+
+  To run this demo you must build it first and push it on your device:
+  # adb push uiautomator.samples.jar /data/local/tmp
+  # adb shell uiautomator runtest uiautomator.samples.jar -c com.android.test.uiautomator.demos.LaunchSettings
+
+---------- LogBuildNumber Demo ------------
+
+  This demos how we can scroll list views and verify data in list view
+  items. Here we do the following:
+   + Launch Settings
+   + Select the About
+   + Read the Build string
+
+  To run this demo you must build it first and push it on your device:
+  # adb push uiautomator.samples.jar /data/local/tmp
+  # adb shell uiautomator runtest uiautomator.samples.jar -c com.android.test.uiautomator.demos.LogBuildNumber
+
+---------- SetTwoMinuteAlarm Demo ---------
+
+  Test demonstrates using the UiAutomator APIs to set an alarm to
+  go off in 2 minutes
+
+  To run this demo you must build it first and push it on your device:
+  # adb push uiautomator.samples.jar /data/local/tmp
+  # adb shell uiautomator runtest uiautomator.samples.jar -c com.android.test.uiautomator.demos.SetTwoMinuteAlarm
diff --git a/samples/UiAutomator/project.properties b/samples/UiAutomator/project.properties
new file mode 100644
index 0000000..86f3b82
--- /dev/null
+++ b/samples/UiAutomator/project.properties
@@ -0,0 +1 @@
+target=android-16
diff --git a/samples/UiAutomator/src/com/android/test/uiautomator/demos/LaunchSettings.java b/samples/UiAutomator/src/com/android/test/uiautomator/demos/LaunchSettings.java
new file mode 100644
index 0000000..112da52
--- /dev/null
+++ b/samples/UiAutomator/src/com/android/test/uiautomator/demos/LaunchSettings.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uiautomator.demos;
+
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiScrollable;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+/**
+ * This demos how we read content-description to properly open the
+ * All Apps view and select and application to launch. Then we will
+ * use the package name to verify that the current window is actually
+ * from the expected package
+ */
+public class LaunchSettings extends UiAutomatorTestCase {
+
+    public void testDemo() throws UiObjectNotFoundException {
+        // Good practice to start from a common place
+        getUiDevice().pressHome();
+
+        // When we use the uiautomatorviewer in the DSK/tools we find that this
+        // button has the following content-description
+        UiObject allAppsButton = new UiObject(new UiSelector().description("Apps"));
+
+        // ** NOTE **
+        // Any operation on a UiObject that is not currently present on the display
+        // will result in a UiObjectNotFoundException to be thrown. If we want to
+        // first check if the object exists we can use allAppsButton.exists() which
+        // return boolean.
+        // ** NOTE **
+
+        //The operation below expects the click will result a new  window.
+        allAppsButton.clickAndWaitForNewWindow();
+
+        // On the this view, we expect two tabs, one for APPS and another for
+        // WIDGETS. We will want to click the APPS just so we're sure apps and
+        // not widgets is our current display
+        UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
+
+        // ** NOTE **
+        // The above operation assumes that there is only one text "Apps" in the
+        // current view. If not, then the first "Apps" is selected. But if we
+        // want to be certain that we click on the tab "Apps", we can use the
+        // uiautomatorview from the SDK/tools and see if we can further narrow the
+        // selector. Comment the line above and uncomment the one bellow to use
+        // the more specific selector.
+        // ** NOTE **
+
+        // This creates a selector hierarchy where the first selector is for the
+        // TabWidget and the second will search only inside the layout of TabWidget
+        // To use this instead, uncomment the lines bellow and comment the above appsTab
+        //UiSelector appsTabSelector =
+        //        new UiSelector().className(android.widget.TabWidget.class.getName())
+        //            .childSelector(new UiSelector().text("Apps"));
+        //UiObject appsTab = new UiObject(appsTabSelector);
+
+
+        // The operation below we only cause a content change so a click() is good
+        appsTab.click();
+
+        // Since our device may have many apps on it spanning multiple views, we
+        // may need to scroll to find our app. Here we use UiScrollable to help.
+        // We declare the object with a selector to a scrollable view. Since in this
+        // case the firt scrollable view happens to be the one containing our apps
+        // list, we should be ok.
+        UiScrollable appViews = new UiScrollable(new UiSelector().scrollable(true));
+        // swipe horizontally when searching (default is vertical)
+        appViews.setAsHorizontalList();
+
+        // the appsViews will perform horizontal scrolls to find the Settings app
+        UiObject settingsApp = appViews.getChildByText(
+                new UiSelector().className(android.widget.TextView.class.getName()), "Settings");
+        settingsApp.clickAndWaitForNewWindow();
+
+        // create a selector for anything on the display and check if the package name
+        // is the expected one
+        UiObject settingsValidation =
+                new UiObject(new UiSelector().packageName("com.android.settings"));
+
+        assertTrue("Unable to detect Settings", settingsValidation.exists());
+    }
+}
diff --git a/samples/UiAutomator/src/com/android/test/uiautomator/demos/LogBuildNumber.java b/samples/UiAutomator/src/com/android/test/uiautomator/demos/LogBuildNumber.java
new file mode 100644
index 0000000..22e4fdb
--- /dev/null
+++ b/samples/UiAutomator/src/com/android/test/uiautomator/demos/LogBuildNumber.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uiautomator.demos;
+
+import android.util.Log;
+
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiScrollable;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+/**
+ * This demos how we can scroll list views and verify data in list view
+ * items. Here we do the following:
+ * <ul>
+ * <li> Launch Settings </li>
+ * <li> Select the About </li>
+ * <li> Read the Build string </li>
+ * </ul>
+ */
+public class LogBuildNumber extends UiAutomatorTestCase {
+    public static final String LOG_TAG = LogBuildNumber.class.getSimpleName();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    /**
+     * For the purpose of this demo, we're declaring the Launcher signatures here.
+     * It may be more appropriate to declare signatures and methods related
+     * to Launcher in their own reusable Launcher helper file.
+     */
+    public static class LauncherHelper {
+        public static final UiSelector ALL_APPS_BUTTON = new UiSelector().description("Apps");
+        public static final UiSelector LAUNCHER_CONTAINER = new UiSelector().scrollable(true);
+        public static final UiSelector LAUNCHER_ITEM =
+                new UiSelector().className(android.widget.TextView.class.getName());
+    }
+
+    /**
+     * For the purpose of this demo, we're declaring the Settings signatures here.
+     * It may be more appropriate to declare signatures and methods related
+     * to Settings in their own reusable Settings helper file.
+     */
+    public static class SettingsHelper {
+        public static final UiSelector LIST_VIEW =
+                new UiSelector().className(android.widget.ListView.class.getName());
+        public static final UiSelector LIST_VIEW_ITEM =
+                new UiSelector().className(android.widget.LinearLayout.class.getName());
+    }
+
+    /**
+     * Script starts here
+     * @throws UiObjectNotFoundException
+     */
+    public void testDemo() throws UiObjectNotFoundException {
+        // The following code is documented in the LaunchSettings demo. For detailed description
+        // of how this code works, look at the demo LaunchSettings
+
+        // Good to start from here
+        getUiDevice().pressHome();
+
+        // open the All Apps view
+        UiObject allAppsButton = new UiObject(LauncherHelper.ALL_APPS_BUTTON);
+        allAppsButton.click();
+
+        // clicking the APPS tab
+        UiSelector appsTabSelector =
+                new UiSelector().className(android.widget.TabWidget.class.getName())
+                    .childSelector(new UiSelector().text("Apps"));
+        UiObject appsTab = new UiObject(appsTabSelector);
+        appsTab.click();
+
+        // Clicking the Settings
+        UiScrollable allAppsScreen = new UiScrollable(LauncherHelper.LAUNCHER_CONTAINER);
+        allAppsScreen.setAsHorizontalList();
+        UiObject settingsApp =
+                allAppsScreen.getChildByText(LauncherHelper.LAUNCHER_ITEM, "Settings");
+        settingsApp.click();
+
+        // Now we will select the settings we need to work with. To make this operation a little
+        // more generic we will put it in a function. We will try it as a phone first then as a
+        // tablet if phone is not our device type
+        if (!selectSettingsFor("About phone"))
+            selectSettingsFor("About tablet");
+
+        // Now we need to read the Build number text and return it
+        String buildNum = getAboutItem("Build number");
+
+        // Log it - Use adb logcat to view the results
+        Log.i(LOG_TAG, "Build = " + buildNum);
+    }
+
+    /**
+     * Select a settings items and perform scroll if needed to find it.
+     * @param name
+     */
+    private boolean selectSettingsFor(String name)  {
+        try {
+            UiScrollable appsSettingsList = new UiScrollable(SettingsHelper.LIST_VIEW);
+            UiObject obj = appsSettingsList.getChildByText(SettingsHelper.LIST_VIEW_ITEM, name);
+            obj.click();
+        } catch (UiObjectNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * This function will detect the presence of 2 or 1 list view display fragments and
+     * targets the correct list view for the About item details
+     * @param item
+     * @return the details string of an about item entry
+     * @throws UiObjectNotFoundException
+     */
+    private String getAboutItem(String item) throws UiObjectNotFoundException {
+        // try accessing the second list view if one exists else we will assume the
+        // device is displaying a single list view
+        UiScrollable aboutSettingsList = new UiScrollable(SettingsHelper.LIST_VIEW.instance(1));
+        if (!aboutSettingsList.exists())
+            aboutSettingsList = new UiScrollable(SettingsHelper.LIST_VIEW.instance(0));
+
+        // the returned aboutItem will be pointing at the node matching the
+        // SettingsOsr.LIST_VIEW_ITEM. So, aboutItem is a container of widgets where one
+        // actually contains the text (item) we're looking for.
+        UiObject aboutItem = aboutSettingsList.getChildByText(SettingsHelper.LIST_VIEW_ITEM, item);
+
+        // Since aboutItem contains the text widgets for the requested details, we're assuming
+        // here that the param 'item' refers to the label and the second text is the value for it.
+        UiObject txt = aboutItem.getChild(
+                new UiSelector().className(android.widget.TextView.class.getName()).instance(1));
+
+        // This is interesting. Since aboutItem is returned pointing a the layout containing the
+        // test values, we know it is visible else an exception would've been thrown. However,
+        // we're not certain that the instance(1) or second text view inside this layout is
+        // in fact fully visible and not off the screen.
+        if (!txt.exists())
+            aboutSettingsList.scrollForward(); // scroll it into view
+
+        return txt.getText();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+}
diff --git a/samples/UiAutomator/src/com/android/test/uiautomator/demos/SetTwoMinuteAlarm.java b/samples/UiAutomator/src/com/android/test/uiautomator/demos/SetTwoMinuteAlarm.java
new file mode 100644
index 0000000..6f970a7
--- /dev/null
+++ b/samples/UiAutomator/src/com/android/test/uiautomator/demos/SetTwoMinuteAlarm.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uiautomator.demos;
+
+import android.widget.TextView;
+
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiScrollable;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.testrunner.UiAutomatorTestCase;
+
+/**
+ * Test demonstrates using the UiAutomator APIs to set an alarm to
+ * go off in 2 minutes
+ */
+public class SetTwoMinuteAlarm extends UiAutomatorTestCase {
+
+    /**
+     * For the purpose of this demo, we're declaring the Launcher signatures here.
+     * It may be more appropriate to declare signatures and methods related
+     * to Launcher in their own reusable Launcher helper file.
+     */
+    public static class LauncherHelper {
+        public static final UiSelector ALL_APPS_BUTTON = new UiSelector().description("Apps");
+        public static final UiSelector LAUNCHER_CONTAINER = new UiSelector().scrollable(true);
+        public static final UiSelector LAUNCHER_ITEM =
+                new UiSelector().className(android.widget.TextView.class.getName());
+    }
+
+    /**
+     * Set an alarm 2 minutes from now and verify it goes off. Also check the notification
+     * shades for an alarm. (Crude test but it demos the use of Android resources)
+     * @throws UiObjectNotFoundException
+     */
+    public void testDemo() throws UiObjectNotFoundException {
+        // The following code is documented in the LaunchSettings demo. For detailed description
+        // of how this code works, look at the demo LaunchSettings
+
+        // Good to start from here
+        getUiDevice().pressHome();
+
+        // open the All Apps view
+        UiObject allAppsButton = new UiObject(LauncherHelper.ALL_APPS_BUTTON);
+        allAppsButton.click();
+
+        // clicking the APPS tab
+        UiSelector appsTabSelector =
+                new UiSelector().className(android.widget.TabWidget.class.getName())
+                    .childSelector(new UiSelector().text("Apps"));
+        UiObject appsTab = new UiObject(appsTabSelector);
+        appsTab.click();
+
+        // Clicking the Settings
+        UiScrollable allAppsScreen = new UiScrollable(LauncherHelper.LAUNCHER_CONTAINER);
+        allAppsScreen.setAsHorizontalList();
+        UiObject clockApp =
+                allAppsScreen.getChildByText(LauncherHelper.LAUNCHER_ITEM, "Clock");
+        clockApp.click();
+
+        // Set an alarm to go off in about 2 minutes
+        setAlarm(2);
+
+        // wait for the alarm alert dialog
+        UiObject alarmAlert =
+                new UiObject(new UiSelector().packageName("com.google.android.deskclock")
+                        .className(TextView.class.getName()).text("Alarm"));
+
+        assertTrue("Timeout while waiting for alarm to go off",
+                alarmAlert.waitForExists(2 * 60 * 1000));
+
+        clickByText("Dismiss");
+    }
+
+    /**
+     * Helper function to set an alarm
+     * @param minutesFromNow
+     * @throws UiObjectNotFoundException
+     */
+    private void setAlarm(int minutesFromNow) throws UiObjectNotFoundException {
+        UiObject setAlarm = new UiObject(new UiSelector().textStartsWith("Alarm set"));
+        if (!setAlarm.exists())
+            setAlarm = new UiObject(new UiSelector().textStartsWith("Set alarm"));
+        setAlarm.click();
+
+        // let's add an alarm
+        clickByDescription("Add alarm");
+        // let's set the time
+        clickByText("Time");
+
+        // we want the minutes only
+        UiSelector minuteAreaSelector = new UiSelector().className(
+                android.widget.NumberPicker.class.getName()).instance(1);
+        UiSelector minuteIncreaseButtonSelector = minuteAreaSelector.childSelector(
+                new UiSelector().className(android.widget.Button.class.getName()).instance(1));
+
+        // increment minutes a couple of times
+        for (int x = 0; x < minutesFromNow; x++)
+            new UiObject(minuteIncreaseButtonSelector).click();
+        clickByText("Done");
+
+        // few confirmations to click thru
+        UiObject doneButton = new UiObject(new UiSelector().text("Done"));
+        UiObject okButton = new UiObject(new UiSelector().text("OK"));
+        // working around some inconsistencies in phone vs tablet UI
+        if (doneButton.exists()) {
+            doneButton.click();
+        } else {
+            okButton.click(); // let it fail if neither exists
+        }
+
+        // we're done. Let's return to home screen
+        clickByText("Done");
+        getUiDevice().pressHome();
+    }
+
+    /**
+     * Helper to click on objects that match the content-description text
+     * @param text
+     * @throws UiObjectNotFoundException
+     */
+    private void clickByDescription(String text) throws UiObjectNotFoundException {
+        UiObject obj = new UiObject(new UiSelector().description(text));
+        obj.clickAndWaitForNewWindow();
+    }
+
+    /**
+     * Helper to click on object that match the text value
+     * @param text
+     * @throws UiObjectNotFoundException
+     */
+    private void clickByText(String text) throws UiObjectNotFoundException {
+        UiObject obj = new UiObject(new UiSelector().text(text));
+        obj.clickAndWaitForNewWindow();
+    }
+}
\ No newline at end of file
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/body.png b/samples/WeatherListWidget/res/drawable-hdpi/body.png
deleted file mode 100644
index 17d303f..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/body.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/footer.png b/samples/WeatherListWidget/res/drawable-hdpi/footer.png
deleted file mode 100644
index 43962f7..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/footer.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/header.9.png b/samples/WeatherListWidget/res/drawable-hdpi/header.9.png
deleted file mode 100644
index 5f34768..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/header.9.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png
deleted file mode 100644
index f5886bd..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_dark.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png b/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png
deleted file mode 100644
index e8b5aaf..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/item_bg_light.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/refresh.png b/samples/WeatherListWidget/res/drawable-hdpi/refresh.png
deleted file mode 100644
index eaec9cb..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/refresh.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png b/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png
deleted file mode 100644
index 34438b7..0000000
--- a/samples/WeatherListWidget/res/drawable-hdpi/refresh_pressed.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-hdpi/sunny.png b/samples/WeatherListWidget/res/drawable-hdpi/sunny.png
new file mode 100644
index 0000000..42785b9
--- /dev/null
+++ b/samples/WeatherListWidget/res/drawable-hdpi/sunny.png
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/body.png b/samples/WeatherListWidget/res/drawable-mdpi/body.png
deleted file mode 100644
index a08d03b..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/body.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/footer.png b/samples/WeatherListWidget/res/drawable-mdpi/footer.png
deleted file mode 100644
index d3960a7..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/footer.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/header.9.png b/samples/WeatherListWidget/res/drawable-mdpi/header.9.png
deleted file mode 100644
index 2372225..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/header.9.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png
deleted file mode 100644
index a3ac9d7..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_dark.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png b/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png
deleted file mode 100644
index ec6f5aa..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/item_bg_light.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/refresh.png b/samples/WeatherListWidget/res/drawable-mdpi/refresh.png
deleted file mode 100644
index 006bcc5..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/refresh.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png b/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png
deleted file mode 100644
index d8ca9b5..0000000
--- a/samples/WeatherListWidget/res/drawable-mdpi/refresh_pressed.png
+++ /dev/null
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-mdpi/sunny.png b/samples/WeatherListWidget/res/drawable-mdpi/sunny.png
new file mode 100644
index 0000000..9453447
--- /dev/null
+++ b/samples/WeatherListWidget/res/drawable-mdpi/sunny.png
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable-nodpi/preview.png b/samples/WeatherListWidget/res/drawable-nodpi/preview.png
index f0cbdaf..b9c8780 100644
--- a/samples/WeatherListWidget/res/drawable-nodpi/preview.png
+++ b/samples/WeatherListWidget/res/drawable-nodpi/preview.png
Binary files differ
diff --git a/samples/WeatherListWidget/res/drawable/refresh_button.xml b/samples/WeatherListWidget/res/drawable/refresh_button.xml
deleted file mode 100644
index 1c0017e..0000000
--- a/samples/WeatherListWidget/res/drawable/refresh_button.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true"
-        android:drawable="@drawable/refresh_pressed" /> <!-- pressed -->
-    <item android:drawable="@drawable/refresh" /> <!-- default -->
-</selector>
\ No newline at end of file
diff --git a/samples/WeatherListWidget/res/layout/dark_widget_item.xml b/samples/WeatherListWidget/res/layout/dark_widget_item.xml
deleted file mode 100644
index 1f920a2..0000000
--- a/samples/WeatherListWidget/res/layout/dark_widget_item.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/widget_item"
-    android:layout_width="match_parent"
-    android:layout_height="46dp"
-    android:paddingLeft="25dp"
-    android:gravity="center_vertical"
-    android:background="@drawable/item_bg_dark"
-    android:textColor="#e5e5e1"
-    android:textSize="24sp" />
diff --git a/samples/WeatherListWidget/res/layout/light_widget_item.xml b/samples/WeatherListWidget/res/layout/light_widget_item.xml
deleted file mode 100644
index bb2946f..0000000
--- a/samples/WeatherListWidget/res/layout/light_widget_item.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/widget_item"
-    android:layout_width="match_parent"
-    android:layout_height="46dp"
-    android:paddingLeft="25dp"
-    android:gravity="center_vertical"
-    android:background="@drawable/item_bg_light"
-    android:textColor="#e5e5e1"
-    android:textSize="24sp" />
diff --git a/samples/WeatherListWidget/res/layout/widget_item.xml b/samples/WeatherListWidget/res/layout/widget_item.xml
new file mode 100644
index 0000000..c0002e5
--- /dev/null
+++ b/samples/WeatherListWidget/res/layout/widget_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/widget_item"
+    android:layout_width="match_parent"
+    android:layout_height="46dp"
+    android:paddingLeft="25dp"
+    android:gravity="center_vertical"
+    android:background="#F0F0F0"
+    android:textColor="#232323"
+    android:textSize="20sp" />
diff --git a/samples/WeatherListWidget/res/layout/widget_layout.xml b/samples/WeatherListWidget/res/layout/widget_layout.xml
index 4c58fa7..f3eb6de 100644
--- a/samples/WeatherListWidget/res/layout/widget_layout.xml
+++ b/samples/WeatherListWidget/res/layout/widget_layout.xml
@@ -23,30 +23,37 @@
     android:layout_marginRight="@dimen/widget_margin_right">
     <!-- We define separate margins to allow for flexibility in twiddling the margins
          depending on device form factor and target SDK version. -->
-    <FrameLayout
+
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="80dp"
+        android:background="#F8F8F8"
+        android:orientation="horizontal">
         <ImageView
-            android:id="@+id/header"
-            android:layout_width="match_parent"
+            android:id="@+id/city_weather"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:scaleType="fitXY"
-            android:src="@drawable/header" />
-        <ImageButton
-            android:id="@+id/refresh"
-            android:layout_width="56dp"
-            android:layout_height="39dp"
-            android:layout_gravity="right|top"
-            android:layout_marginRight="15dp"
-            android:layout_marginTop="20dp"
-            android:background="@drawable/refresh_button" />
-    </FrameLayout>
+            android:padding="12dp"
+            android:scaleType="fitStart"
+            android:adjustViewBounds="true"
+            android:src="@drawable/sunny" />
+        <TextView
+            android:id="@+id/city_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="@string/city_name"
+            android:textAllCaps="true"
+            android:textColor="#232323"
+            android:textSize="24sp" />
+    </LinearLayout>
+
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_weight="1"
         android:layout_gravity="center"
-        android:background="@drawable/body">
+        android:background="#F8F8F8">
         <ListView
             android:id="@+id/weather_list"
             android:layout_width="match_parent"
@@ -61,10 +68,16 @@
             android:text="@string/empty_view_text"
             android:textSize="20sp" />
     </FrameLayout>
-    <ImageView
-        android:id="@+id/footer"
+
+    <Button
+        android:id="@+id/refresh"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:scaleType="fitXY"
-        android:src="@drawable/footer" />
+        android:padding="12dp"
+        android:gravity="center"
+        android:background="#F8F8F8"
+        android:text="@string/refresh"
+        android:textAllCaps="true"
+        android:textColor="#232323"
+        android:textSize="14sp" />
 </LinearLayout>
diff --git a/samples/WeatherListWidget/res/layout/widget_layout_small.xml b/samples/WeatherListWidget/res/layout/widget_layout_small.xml
new file mode 100644
index 0000000..20227b7
--- /dev/null
+++ b/samples/WeatherListWidget/res/layout/widget_layout_small.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_marginTop="@dimen/widget_margin_top"
+    android:layout_marginBottom="@dimen/widget_margin_bottom"
+    android:layout_marginLeft="@dimen/widget_margin_left"
+    android:layout_marginRight="@dimen/widget_margin_right">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="80dp"
+        android:background="#F8F8F8"
+        android:orientation="horizontal">
+        <ImageView
+            android:id="@+id/city_weather"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="12dp"
+            android:scaleType="fitStart"
+            android:adjustViewBounds="true"
+            android:src="@drawable/sunny" />
+        <TextView
+            android:id="@+id/city_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:text="@string/city_name"
+            android:textAllCaps="true"
+            android:textColor="#232323"
+            android:textSize="24sp" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/samples/WeatherListWidget/res/values/strings.xml b/samples/WeatherListWidget/res/values/strings.xml
index 6542545..a3b7e06 100644
--- a/samples/WeatherListWidget/res/values/strings.xml
+++ b/samples/WeatherListWidget/res/values/strings.xml
@@ -15,6 +15,10 @@
 -->
 <resources>
     <string name="empty_view_text">No cities found...</string>
-    <string name="toast_format_string">%1$s says Hi!</string>
-    <string name="item_format_string">%1$d\u00B0 in %2$s</string>
+    <string name="toast_format_string">%1$s!</string>
+    <string name="item_format_string">%1$d\u00B0 on %2$s</string>
+    <string name="header_format_string">%1$d\u00B0 in %2$s</string>
+
+    <string name="refresh">Refresh</string>
+    <string name="city_name">San Francisco</string>
 </resources>
diff --git a/samples/WeatherListWidget/res/xml/widgetinfo.xml b/samples/WeatherListWidget/res/xml/widgetinfo.xml
index e6e9cf3..2e41943 100644
--- a/samples/WeatherListWidget/res/xml/widgetinfo.xml
+++ b/samples/WeatherListWidget/res/xml/widgetinfo.xml
@@ -15,12 +15,12 @@
 -->
 <appwidget-provider
   xmlns:android="http://schemas.android.com/apk/res/android"
-  android:minWidth="250dp"
+  android:minWidth="280dp"
   android:minHeight="180dp"
   android:updatePeriodMillis="1800000"
   android:initialLayout="@layout/widget_layout"
   android:resizeMode="vertical"
-  android:minResizeWidth="250dp"
-  android:minResizeHeight="110dp"
+  android:minResizeWidth="280dp"
+  android:minResizeHeight="70dp"
   android:previewImage="@drawable/preview">
 </appwidget-provider>
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java
index 92a1cb3..ede0039 100644
--- a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherDataProvider.java
@@ -36,12 +36,12 @@
  * data will only be stored in memory.
  */
 class WeatherDataPoint {
-    String city;
+    String day;
     int degrees;
 
-    WeatherDataPoint(String c, int d) {
-        city = c;
-        degrees = d;
+    WeatherDataPoint(String d, int deg) {
+        day = d;
+        degrees = deg;
     }
 }
 
@@ -53,7 +53,7 @@
         Uri.parse("content://com.example.android.weatherlistwidget.provider");
     public static class Columns {
         public static final String ID = "_id";
-        public static final String CITY = "city";
+        public static final String DAY = "day";
         public static final String TEMPERATURE = "temperature";
     }
 
@@ -67,17 +67,20 @@
     @Override
     public boolean onCreate() {
         // We are going to initialize the data provider with some default values
-        sData.add(new WeatherDataPoint("San Francisco", 13));
-        sData.add(new WeatherDataPoint("New York", 1));
-        sData.add(new WeatherDataPoint("Seattle", 7));
-        sData.add(new WeatherDataPoint("Boston", 4));
-        sData.add(new WeatherDataPoint("Miami", 22));
-        sData.add(new WeatherDataPoint("Toronto", -10));
-        sData.add(new WeatherDataPoint("Calgary", -13));
-        sData.add(new WeatherDataPoint("Tokyo", 8));
-        sData.add(new WeatherDataPoint("Kyoto", 11));
-        sData.add(new WeatherDataPoint("London", -1));
-        sData.add(new WeatherDataPoint("Nomanisan", 27));
+        sData.add(new WeatherDataPoint("Monday", 13));
+        sData.add(new WeatherDataPoint("Tuesday", 1));
+        sData.add(new WeatherDataPoint("Wednesday", 7));
+        sData.add(new WeatherDataPoint("Thursday", 4));
+        sData.add(new WeatherDataPoint("Friday", 22));
+        sData.add(new WeatherDataPoint("Saturday", -10));
+        sData.add(new WeatherDataPoint("Sunday", -13));
+        sData.add(new WeatherDataPoint("Monday", 8));
+        sData.add(new WeatherDataPoint("Tuesday", 11));
+        sData.add(new WeatherDataPoint("Wednesday", -1));
+        sData.add(new WeatherDataPoint("Thursday", 27));
+        sData.add(new WeatherDataPoint("Friday", 27));
+        sData.add(new WeatherDataPoint("Saturday", 27));
+        sData.add(new WeatherDataPoint("Sunday", 27));
         return true;
     }
 
@@ -89,17 +92,17 @@
         // In this sample, we only query without any parameters, so we can just return a cursor to
         // all the weather data.
         final MatrixCursor c = new MatrixCursor(
-                new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE });
+                new String[]{ Columns.ID, Columns.DAY, Columns.TEMPERATURE });
         for (int i = 0; i < sData.size(); ++i) {
             final WeatherDataPoint data = sData.get(i);
-            c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) });
+            c.addRow(new Object[]{ new Integer(i), data.day, new Integer(data.degrees) });
         }
         return c;
     }
 
     @Override
     public String getType(Uri uri) {
-        return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature";
+        return "vnd.android.cursor.dir/vnd.weatherlistwidget.temperature";
     }
 
     @Override
@@ -123,7 +126,7 @@
         // temperature values.
         final int index = Integer.parseInt(uri.getPathSegments().get(0));
         final MatrixCursor c = new MatrixCursor(
-                new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE });
+                new String[]{ Columns.ID, Columns.DAY, Columns.TEMPERATURE });
         assert(0 <= index && index < sData.size());
         final WeatherDataPoint data = sData.get(index);
         data.degrees = values.getAsInteger(Columns.TEMPERATURE);
@@ -134,4 +137,4 @@
         return 1;
     }
 
-}
\ No newline at end of file
+}
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java
index 2f2b347..ea3f944 100644
--- a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetProvider.java
@@ -28,6 +28,7 @@
 import android.database.Cursor;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.widget.RemoteViews;
@@ -64,11 +65,15 @@
 public class WeatherWidgetProvider extends AppWidgetProvider {
     public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK";
     public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH";
-    public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city";
+    public static String EXTRA_DAY_ID = "com.example.android.weatherlistwidget.day";
 
     private static HandlerThread sWorkerThread;
     private static Handler sWorkerQueue;
     private static WeatherDataProviderObserver sDataObserver;
+    private static final int sMaxDegrees = 96;
+
+    private boolean mIsLargeLayout = true;
+    private int mHeaderWeatherState = 0;
 
     public WeatherWidgetProvider() {
         // Start the worker thread
@@ -77,6 +82,8 @@
         sWorkerQueue = new Handler(sWorkerThread.getLooper());
     }
 
+    // XXX: clear the worker queue if we are destroyed?
+
     @Override
     public void onEnabled(Context context) {
         // Register for external updates to the data to trigger an update of the widget.  When using
@@ -109,7 +116,6 @@
                     final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null, 
                             null);
                     final int count = c.getCount();
-                    final int maxDegrees = 96;
 
                     // We disable the data changed observer temporarily since each of the updates
                     // will trigger an onChange() in our data observer.
@@ -118,7 +124,7 @@
                         final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i);
                         final ContentValues values = new ContentValues();
                         values.put(WeatherDataProvider.Columns.TEMPERATURE,
-                                new Random().nextInt(maxDegrees));
+                                new Random().nextInt(sMaxDegrees));
                         r.update(uri, values, null, null);
                     }
                     r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver);
@@ -128,29 +134,31 @@
                     mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list);
                 }
             });
+
+            final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    AppWidgetManager.INVALID_APPWIDGET_ID);
         } else if (action.equals(CLICK_ACTION)) {
             // Show a toast
             final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                     AppWidgetManager.INVALID_APPWIDGET_ID);
-            final String city = intent.getStringExtra(EXTRA_CITY_ID);
+            final String day = intent.getStringExtra(EXTRA_DAY_ID);
             final String formatStr = ctx.getResources().getString(R.string.toast_format_string);
-            Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show();
+            Toast.makeText(ctx, String.format(formatStr, day), Toast.LENGTH_SHORT).show();
         }
 
         super.onReceive(ctx, intent);
     }
 
-    @Override
-    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
-        // Update each of the widgets with the remote adapter
-        for (int i = 0; i < appWidgetIds.length; ++i) {
+    private RemoteViews buildLayout(Context context, int appWidgetId, boolean largeLayout) {
+        RemoteViews rv;
+        if (largeLayout) {
             // Specify the service to provide data for the collection widget.  Note that we need to
             // embed the appWidgetId via the data otherwise it will be ignored.
             final Intent intent = new Intent(context, WeatherWidgetService.class);
-            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
             intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
-            final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
-            rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent);
+            rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
+            rv.setRemoteAdapter(appWidgetId, R.id.weather_list, intent);
 
             // Set the empty view to be displayed if the collection is empty.  It must be a sibling
             // view of the collection view.
@@ -161,7 +169,7 @@
             // ignored otherwise.
             final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class);
             onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION);
-            onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
+            onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
             onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME)));
             final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0,
                     onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -174,8 +182,53 @@
                     refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
             rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent);
 
-            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
+            // Restore the minimal header
+            rv.setTextViewText(R.id.city_name, context.getString(R.string.city_name));
+        } else {
+            rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout_small);
+
+            // Update the header to reflect the weather for "today"
+            Cursor c = context.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null,
+                    null, null, null);
+            if (c.moveToPosition(0)) {
+                int tempColIndex = c.getColumnIndex(WeatherDataProvider.Columns.TEMPERATURE);
+                int temp = c.getInt(tempColIndex);
+                String formatStr = context.getResources().getString(R.string.header_format_string);
+                String header = String.format(formatStr, temp,
+                        context.getString(R.string.city_name));
+                rv.setTextViewText(R.id.city_name, header);
+            }
+            c.close();
+        }
+        return rv;
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        // Update each of the widgets with the remote adapter
+        for (int i = 0; i < appWidgetIds.length; ++i) {
+            RemoteViews layout = buildLayout(context, appWidgetIds[i], mIsLargeLayout);
+            appWidgetManager.updateAppWidget(appWidgetIds[i], layout);
         }
         super.onUpdate(context, appWidgetManager, appWidgetIds);
     }
+
+    @Override
+    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
+            int appWidgetId, Bundle newOptions) {
+
+        int minWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+        int maxWidth = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+        int minHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+        int maxHeight = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+
+        RemoteViews layout;
+        if (minHeight < 100) {
+            mIsLargeLayout = false;
+        } else {
+            mIsLargeLayout = true;
+        }
+        layout = buildLayout(context, appWidgetId, mIsLargeLayout);
+        appWidgetManager.updateAppWidget(appWidgetId, layout);
+    }
 }
\ No newline at end of file
diff --git a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java
index 1d3c349..4780e80 100644
--- a/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java
+++ b/samples/WeatherListWidget/src/com/example/android/weatherlistwidget/WeatherWidgetService.java
@@ -70,28 +70,26 @@
 
     public RemoteViews getViewAt(int position) {
         // Get the data for this position from the content provider
-        String city = "Unknown City";
+        String day = "Unknown Day";
         int temp = 0;
         if (mCursor.moveToPosition(position)) {
-            final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY);
+            final int dayColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.DAY);
             final int tempColIndex = mCursor.getColumnIndex(
                     WeatherDataProvider.Columns.TEMPERATURE);
-            city = mCursor.getString(cityColIndex);
+            day = mCursor.getString(dayColIndex);
             temp = mCursor.getInt(tempColIndex);
         }
 
-        // Return a proper item with the proper city and temperature.  Just for fun, we alternate
-        // the items to make the list easier to read.
+        // Return a proper item with the proper day and temperature
         final String formatStr = mContext.getResources().getString(R.string.item_format_string);
-        final int itemId = (position % 2 == 0 ? R.layout.light_widget_item
-                : R.layout.dark_widget_item);
+        final int itemId = R.layout.widget_item;
         RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId);
-        rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city));
+        rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, day));
 
         // Set the click intent so that we can handle it and show a toast message
         final Intent fillInIntent = new Intent();
         final Bundle extras = new Bundle();
-        extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city);
+        extras.putString(WeatherWidgetProvider.EXTRA_DAY_ID, day);
         fillInIntent.putExtras(extras);
         rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
 
diff --git a/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml b/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..eeb5378
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.activityanim"
+          android:versionCode="1"
+          android:versionName="1.0" >
+
+    <uses-sdk
+            android:minSdkVersion="16"
+            android:targetSdkVersion="17" />
+
+    <application
+            android:allowBackup="true"
+            android:icon="@drawable/ic_launcher"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme" >
+        <activity
+                android:name="com.example.android.activityanim.ActivityAnimations"
+                android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+                android:name="com.example.android.activityanim.PictureDetailsActivity"
+                android:label="@string/subactivity_name"
+                android:theme="@style/Transparent" >
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg
new file mode 100644
index 0000000..9745818
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg
new file mode 100644
index 0000000..db8731f
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg
new file mode 100644
index 0000000..b240b3a
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg
new file mode 100644
index 0000000..4de9292
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml b/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml
new file mode 100644
index 0000000..c11a568
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml
@@ -0,0 +1,21 @@
+<!-- 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.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_horizontal"
+        android:id="@+id/gridLayout" >
+
+</GridLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml b/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml
new file mode 100644
index 0000000..cb2ced2
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml
@@ -0,0 +1,41 @@
+<!-- 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"
+    android:id="@+id/topLevelLayout">
+    
+    <view
+        class="com.example.android.activityanim.ShadowLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/shadowLayout"
+        android:visibility="visible" >
+
+        <TextView
+            android:id="@+id/description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/imageView" />
+        <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:scaleType="centerInside" />
+    
+    
+    </view>
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml b/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml
new file mode 100644
index 0000000..aab540e
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/menu_slow"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/menu_slow_animations"
+        android:checkable="true"/>
+
+</menu>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml b/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml
new file mode 100644
index 0000000..f9409b5
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!-- 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="app_name">Activity Animations</string>
+    <string name="subactivity_name">PictureInfo!</string>
+    <string name="menu_slow_animations">Slow</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml b/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml
new file mode 100644
index 0000000..9d83342
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml
@@ -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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="Transparent">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java
new file mode 100644
index 0000000..4a3e0d9
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java
@@ -0,0 +1,133 @@
+/*
+ * 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.example.android.activityanim;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+
+/**
+ * This example shows how to create a custom activity animation when you want something more
+ * than window animations can provide. The idea is to disable window animations for the
+ * activities and to instead launch or return from the sub-activity immediately, but use
+ * property animations inside the activities to customize the transition.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class ActivityAnimations extends Activity {
+
+    private static final String PACKAGE = "com.example.android.activityanim";
+    static float sAnimatorScale = 1;
+
+    GridLayout mGridLayout;
+    HashMap<ImageView, PictureData> mPicturesData = new HashMap<ImageView, PictureData>();
+    BitmapUtils mBitmapUtils = new BitmapUtils();
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_animations);
+
+        // Grayscale filter used on all thumbnails
+        ColorMatrix grayMatrix = new ColorMatrix();
+        grayMatrix.setSaturation(0);
+        ColorMatrixColorFilter grayscaleFilter = new ColorMatrixColorFilter(grayMatrix);
+        
+        mGridLayout = (GridLayout) findViewById(R.id.gridLayout);
+        mGridLayout.setColumnCount(3);
+        mGridLayout.setUseDefaultMargins(true);
+        
+        // add all photo thumbnails to layout
+        Resources resources = getResources();
+        ArrayList<PictureData> pictures = mBitmapUtils.loadPhotos(resources);
+        for (int i = 0; i < pictures.size(); ++i) {
+            PictureData pictureData = pictures.get(i);
+            BitmapDrawable thumbnailDrawable =
+                    new BitmapDrawable(resources, pictureData.thumbnail);
+            thumbnailDrawable.setColorFilter(grayscaleFilter);
+            ImageView imageView = new ImageView(this);
+            imageView.setOnClickListener(thumbnailClickListener);
+            imageView.setImageDrawable(thumbnailDrawable);
+            mPicturesData.put(imageView, pictureData);
+            mGridLayout.addView(imageView);
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.activity_better_window_animations, menu);
+        return true;
+    }
+    
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.menu_slow) {
+            sAnimatorScale = item.isChecked() ? 1 : 5;
+            item.setChecked(!item.isChecked());
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    /**
+     * When the user clicks a thumbnail, bundle up information about it and launch the
+     * details activity.
+     */
+    private View.OnClickListener thumbnailClickListener = new View.OnClickListener() {
+        
+        @Override
+        public void onClick(View v) {
+            // Interesting data to pass across are the thumbnail size/location, the
+            // resourceId of the source bitmap, the picture description, and the
+            // orientation (to avoid returning back to an obsolete configuration if
+            // the device rotates again in the meantime)
+            int[] screenLocation = new int[2];
+            v.getLocationOnScreen(screenLocation);
+            PictureData info = mPicturesData.get(v);
+            Intent subActivity = new Intent(ActivityAnimations.this,
+                    PictureDetailsActivity.class);
+            int orientation = getResources().getConfiguration().orientation;
+            subActivity.
+                    putExtra(PACKAGE + ".orientation", orientation).
+                    putExtra(PACKAGE + ".resourceId", info.resourceId).
+                    putExtra(PACKAGE + ".left", screenLocation[0]).
+                    putExtra(PACKAGE + ".top", screenLocation[1]).
+                    putExtra(PACKAGE + ".width", v.getWidth()).
+                    putExtra(PACKAGE + ".height", v.getHeight()).
+                    putExtra(PACKAGE + ".description", info.description);
+            startActivity(subActivity);
+            
+            // Override transitions: we don't want the normal window animation in addition
+            // to our custom one
+            overridePendingTransition(0, 0);
+        }
+    };
+
+}
diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java
new file mode 100644
index 0000000..a8034dc
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.example.android.activityanim;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.widget.ImageView;
+
+public class BitmapUtils {
+    
+    int[] mPhotos = {
+            R.drawable.p1,
+            R.drawable.p2,
+            R.drawable.p3,
+            R.drawable.p4
+    };
+    
+    String[] mDescriptions = {
+            "This picture was taken while sunbathing in a natural hot spring, which was " +
+            "unfortunately filled with acid, which is a lasting memory from that trip, whenever I " +
+            "I look at my own skin.",
+            "I took this shot with a pinhole camera mounted on a tripod constructed out of " +
+            "soda straws. I felt that that combination best captured the beauty of the landscape " +
+            "in juxtaposition with the detritus of mankind.",
+            "I don't remember where or when I took this picture. All I know is that I was really " +
+            "drunk at the time, and I woke up without my left sock.",
+            "Right before I took this picture, there was a busload of school children right " +
+            "in my way. I knew the perfect shot was coming, so I quickly yelled 'Free candy!!!' " +
+            "and they scattered.",
+    };
+
+    static HashMap<Integer, Bitmap> sBitmapResourceMap = new HashMap<Integer, Bitmap>();
+
+    /**
+     * Load pictures and descriptions. A real app wouldn't do it this way, but that's
+     * not the point of this animation demo. Loading asynchronously is a better way to go
+     * for what can be time-consuming operations.
+     */
+    public ArrayList<PictureData> loadPhotos(Resources resources) {
+        ArrayList<PictureData> pictures = new ArrayList<PictureData>();
+        for (int i = 0; i < 30; ++i) {
+            int resourceId = mPhotos[(int) (Math.random() * mPhotos.length)];
+            Bitmap bitmap = getBitmap(resources, resourceId);
+            Bitmap thumbnail = getThumbnail(bitmap, 200);
+            String description = mDescriptions[(int) (Math.random() * mDescriptions.length)];
+            pictures.add(new PictureData(resourceId, description, thumbnail));
+        }
+        return pictures;
+    }
+
+    /**
+     * Utility method to get bitmap from cache or, if not there, load it
+     * from its resource.
+     */
+    static Bitmap getBitmap(Resources resources, int resourceId) {
+        Bitmap bitmap = sBitmapResourceMap.get(resourceId);
+        if (bitmap == null) {
+            bitmap = BitmapFactory.decodeResource(resources, resourceId);
+            sBitmapResourceMap.put(resourceId, bitmap);
+        }        
+        return bitmap;
+    }
+    
+    /**
+     * Create and return a thumbnail image given the original source bitmap and a max
+     * dimension (width or height).
+     */
+    private Bitmap getThumbnail(Bitmap original, int maxDimension) {
+        int width = original.getWidth();
+        int height = original.getHeight();
+        int scaledWidth, scaledHeight;
+        if (width >= height) {
+            float scaleFactor = (float) maxDimension / width;
+            scaledWidth = 200;
+            scaledHeight = (int) (scaleFactor * height);
+        } else {
+            float scaleFactor = (float) maxDimension / height;
+            scaledWidth = (int) (scaleFactor * width);
+            scaledHeight = 200;
+        }
+        Bitmap thumbnail = Bitmap.createScaledBitmap(original, scaledWidth, scaledHeight, true);
+        
+        return thumbnail;
+    }
+    
+
+}
diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java
new file mode 100644
index 0000000..eb2d126
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java
@@ -0,0 +1,31 @@
+/*
+ * 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.example.android.activityanim;
+
+import android.graphics.Bitmap;
+
+public class PictureData {
+    int resourceId;
+    String description;
+    Bitmap thumbnail;
+    
+    public PictureData(int resourceId, String description, Bitmap thumbnail) {
+        this.resourceId = resourceId;
+        this.description = description;
+        this.thumbnail = thumbnail;
+    }
+}
diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java
new file mode 100644
index 0000000..e1674c9
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java
@@ -0,0 +1,280 @@
+/*
+ * 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.example.android.activityanim;
+
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This sub-activity shows a zoomed-in view of a specific photo, along with the
+ * picture's text description. Most of the logic is for the animations that will
+ * be run when the activity is being launched and exited. When launching,
+ * the large version of the picture will resize from the thumbnail version in the
+ * main activity, colorizing it from the thumbnail's grayscale version at the
+ * same time. Meanwhile, the black background of the activity will fade in and
+ * the description will eventually slide into place. The exit animation runs all
+ * of this in reverse.
+ * 
+ */
+public class PictureDetailsActivity extends Activity {
+
+    private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
+    private static final String PACKAGE_NAME = "com.example.android.activityanim";
+    private static final int ANIM_DURATION = 500;
+
+    private BitmapDrawable mBitmapDrawable;
+    private ColorMatrix colorizerMatrix = new ColorMatrix();
+    ColorDrawable mBackground;
+    int mLeftDelta;
+    int mTopDelta;
+    float mWidthScale;
+    float mHeightScale;
+    private ImageView mImageView;
+    private TextView mTextView;
+    private FrameLayout mTopLevelLayout;
+    private ShadowLayout mShadowLayout;
+    private int mOriginalOrientation;
+    
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.picture_info);
+        mImageView = (ImageView) findViewById(R.id.imageView);
+        mTopLevelLayout = (FrameLayout) findViewById(R.id.topLevelLayout);
+        mShadowLayout = (ShadowLayout) findViewById(R.id.shadowLayout);
+        mTextView = (TextView) findViewById(R.id.description);
+        
+        // Retrieve the data we need for the picture/description to display and
+        // the thumbnail to animate it from
+        Bundle bundle = getIntent().getExtras();
+        Bitmap bitmap = BitmapUtils.getBitmap(getResources(),
+                bundle.getInt(PACKAGE_NAME + ".resourceId"));
+        String description = bundle.getString(PACKAGE_NAME + ".description");
+        final int thumbnailTop = bundle.getInt(PACKAGE_NAME + ".top");
+        final int thumbnailLeft = bundle.getInt(PACKAGE_NAME + ".left");
+        final int thumbnailWidth = bundle.getInt(PACKAGE_NAME + ".width");
+        final int thumbnailHeight = bundle.getInt(PACKAGE_NAME + ".height");
+        mOriginalOrientation = bundle.getInt(PACKAGE_NAME + ".orientation");
+        
+        mBitmapDrawable = new BitmapDrawable(getResources(), bitmap);
+        mImageView.setImageDrawable(mBitmapDrawable);
+        mTextView.setText(description);
+        
+        mBackground = new ColorDrawable(Color.BLACK);
+        mTopLevelLayout.setBackground(mBackground);
+
+        // Only run the animation if we're coming from the parent activity, not if
+        // we're recreated automatically by the window manager (e.g., device rotation)
+        if (savedInstanceState == null) {
+            ViewTreeObserver observer = mImageView.getViewTreeObserver();
+            observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                
+                @Override
+                public boolean onPreDraw() {
+                    mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
+
+                    // Figure out where the thumbnail and full size versions are, relative
+                    // to the screen and each other
+                    int[] screenLocation = new int[2];
+                    mImageView.getLocationOnScreen(screenLocation);
+                    mLeftDelta = thumbnailLeft - screenLocation[0];
+                    mTopDelta = thumbnailTop - screenLocation[1];
+                    
+                    // Scale factors to make the large version the same size as the thumbnail
+                    mWidthScale = (float) thumbnailWidth / mImageView.getWidth();
+                    mHeightScale = (float) thumbnailHeight / mImageView.getHeight();
+    
+                    runEnterAnimation();
+                    
+                    return true;
+                }
+            });
+        }
+    }
+
+    /**
+     * The enter animation scales the picture in from its previous thumbnail
+     * size/location, colorizing it in parallel. In parallel, the background of the
+     * activity is fading in. When the pictue is in place, the text description
+     * drops down.
+     */
+    public void runEnterAnimation() {
+        final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale);
+        
+        // Set starting values for properties we're going to animate. These
+        // values scale and position the full size version down to the thumbnail
+        // size/location, from which we'll animate it back up
+        mImageView.setPivotX(0);
+        mImageView.setPivotY(0);
+        mImageView.setScaleX(mWidthScale);
+        mImageView.setScaleY(mHeightScale);
+        mImageView.setTranslationX(mLeftDelta);
+        mImageView.setTranslationY(mTopDelta);
+        
+        // We'll fade the text in later
+        mTextView.setAlpha(0);
+        
+        // Animate scale and translation to go from thumbnail to full size
+        mImageView.animate().setDuration(duration).
+                scaleX(1).scaleY(1).
+                translationX(0).translationY(0).
+                setInterpolator(sDecelerator).
+                withEndAction(new Runnable() {
+                    public void run() {
+                        // Animate the description in after the image animation
+                        // is done. Slide and fade the text in from underneath
+                        // the picture.
+                        mTextView.setTranslationY(-mTextView.getHeight());
+                        mTextView.animate().setDuration(duration/2).
+                                translationY(0).alpha(1).
+                                setInterpolator(sDecelerator);
+                    }
+                });
+        
+        // Fade in the black background
+        ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
+        bgAnim.setDuration(duration);
+        bgAnim.start();
+        
+        // Animate a color filter to take the image from grayscale to full color.
+        // This happens in parallel with the image scaling and moving into place.
+        ObjectAnimator colorizer = ObjectAnimator.ofFloat(PictureDetailsActivity.this,
+                "saturation", 0, 1);
+        colorizer.setDuration(duration);
+        colorizer.start();
+
+        // Animate a drop-shadow of the image
+        ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout, "shadowDepth", 0, 1);
+        shadowAnim.setDuration(duration);
+        shadowAnim.start();
+    }
+    
+    /**
+     * The exit animation is basically a reverse of the enter animation, except that if
+     * the orientation has changed we simply scale the picture back into the center of
+     * the screen.
+     * 
+     * @param endAction This action gets run after the animation completes (this is
+     * when we actually switch activities)
+     */
+    public void runExitAnimation(final Runnable endAction) {
+        final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale);
+
+        // No need to set initial values for the reverse animation; the image is at the
+        // starting size/location that we want to start from. Just animate to the
+        // thumbnail size/location that we retrieved earlier 
+        
+        // Caveat: configuration change invalidates thumbnail positions; just animate
+        // the scale around the center. Also, fade it out since it won't match up with
+        // whatever's actually in the center
+        final boolean fadeOut;
+        if (getResources().getConfiguration().orientation != mOriginalOrientation) {
+            mImageView.setPivotX(mImageView.getWidth() / 2);
+            mImageView.setPivotY(mImageView.getHeight() / 2);
+            mLeftDelta = 0;
+            mTopDelta = 0;
+            fadeOut = true;
+        } else {
+            fadeOut = false;
+        }
+
+        // First, slide/fade text out of the way
+        mTextView.animate().translationY(-mTextView.getHeight()).alpha(0).
+                setDuration(duration/2).setInterpolator(sAccelerator).
+                withEndAction(new Runnable() {
+                    public void run() {
+                        // Animate image back to thumbnail size/location
+                        mImageView.animate().setDuration(duration).
+                                scaleX(mWidthScale).scaleY(mHeightScale).
+                                translationX(mLeftDelta).translationY(mTopDelta).
+                                withEndAction(endAction);
+                        if (fadeOut) {
+                            mImageView.animate().alpha(0);
+                        }
+                        // Fade out background
+                        ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0);
+                        bgAnim.setDuration(duration);
+                        bgAnim.start();
+
+                        // Animate the shadow of the image
+                        ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout,
+                                "shadowDepth", 1, 0);
+                        shadowAnim.setDuration(duration);
+                        shadowAnim.start();
+
+                        // Animate a color filter to take the image back to grayscale,
+                        // in parallel with the image scaling and moving into place.
+                        ObjectAnimator colorizer =
+                                ObjectAnimator.ofFloat(PictureDetailsActivity.this,
+                                "saturation", 1, 0);
+                        colorizer.setDuration(duration);
+                        colorizer.start();
+                    }
+                });
+
+        
+    }
+
+    /**
+     * Overriding this method allows us to run our exit animation first, then exiting
+     * the activity when it is complete.
+     */
+    @Override
+    public void onBackPressed() {
+        runExitAnimation(new Runnable() {
+            public void run() {
+                // *Now* go ahead and exit the activity
+                finish();
+            }
+        });
+    }
+
+    /**
+     * This is called by the colorizing animator. It sets a saturation factor that is then
+     * passed onto a filter on the picture's drawable.
+     * @param value
+     */
+    public void setSaturation(float value) {
+        colorizerMatrix.setSaturation(value);
+        ColorMatrixColorFilter colorizerFilter = new ColorMatrixColorFilter(colorizerMatrix);
+        mBitmapDrawable.setColorFilter(colorizerFilter);
+    }
+    
+    @Override
+    public void finish() {
+        super.finish();
+        
+        // override transitions to skip the standard window animations
+        overridePendingTransition(0, 0);
+    }
+}
diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java
new file mode 100644
index 0000000..b3bc961
--- /dev/null
+++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java
@@ -0,0 +1,116 @@
+/*
+ * 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.example.android.activityanim;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+/**
+ * This custom layout paints a drop shadow behind all children. The size and opacity
+ * of the drop shadow is determined by a "depth" factor that can be set and animated.
+ */
+public class ShadowLayout extends RelativeLayout {
+
+    Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    float mShadowDepth;
+    Bitmap mShadowBitmap;
+    static final int BLUR_RADIUS = 6;
+    static final RectF sShadowRectF = new RectF(0, 0, 200, 200);
+    static final Rect sShadowRect = new Rect(0, 0, 200 + 2 * BLUR_RADIUS, 200 + 2 * BLUR_RADIUS);
+    static RectF tempShadowRectF = new RectF(0, 0, 0, 0);
+    
+    public ShadowLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    public ShadowLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public ShadowLayout(Context context) {
+        super(context);
+        init();
+    }
+    
+    /**
+     * Called by the constructors - sets up the drawing parameters for the drop shadow.
+     */
+    private void init() {
+        mShadowPaint.setColor(Color.BLACK);
+        mShadowPaint.setStyle(Style.FILL);
+        setWillNotDraw(false);
+        mShadowBitmap = Bitmap.createBitmap(sShadowRect.width(),
+                sShadowRect.height(), Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(mShadowBitmap);
+        mShadowPaint.setMaskFilter(new BlurMaskFilter(BLUR_RADIUS, Blur.NORMAL));
+        c.translate(BLUR_RADIUS, BLUR_RADIUS);
+        c.drawRoundRect(sShadowRectF, sShadowRectF.width() / 40,
+                sShadowRectF.height() / 40, mShadowPaint);
+    }
+    
+    /**
+     * The "depth" factor determines the offset distance and opacity of the shadow (shadows that
+     * are further away from the source are offset greater and are more translucent).
+     * @param depth
+     */
+    public void setShadowDepth(float depth) {
+        if (depth != mShadowDepth) {
+            mShadowDepth = depth;
+            mShadowPaint.setAlpha((int) (100 + 150 * (1 - mShadowDepth)));
+            invalidate(); // We need to redraw when the shadow attributes change
+        }
+    }
+
+    /**
+     * Overriding onDraw allows us to draw shadows behind every child of this container.
+     * onDraw() is called to draw a layout's content before the children are drawn, so the
+     * shadows will be drawn first, behind the children (which is what we want).
+     */
+    @Override
+    protected void onDraw(Canvas canvas) {
+        for (int i = 0; i < getChildCount(); ++i) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != View.VISIBLE || child.getAlpha() == 0) {
+                continue;
+            }
+            int depthFactor = (int) (80 * mShadowDepth);
+            canvas.save();
+            canvas.translate(child.getLeft() + depthFactor,
+                    child.getTop() + depthFactor);
+            canvas.concat(child.getMatrix());
+            tempShadowRectF.right = child.getWidth();
+            tempShadowRectF.bottom = child.getHeight();
+            canvas.drawBitmap(mShadowBitmap, sShadowRect, tempShadowRectF, mShadowPaint);
+            canvas.restore();
+        }
+    }
+    
+    
+}
diff --git a/samples/devbytes/animation/Anticipation/AndroidManifest.xml b/samples/devbytes/animation/Anticipation/AndroidManifest.xml
new file mode 100644
index 0000000..c941524
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.anticipation"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.anticipation.Anticipation"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Anticipation/res/layout/main.xml b/samples/devbytes/animation/Anticipation/res/layout/main.xml
new file mode 100644
index 0000000..7da093f
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/layout/main.xml
@@ -0,0 +1,28 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/container"
+    android:clipChildren="false"
+    tools:context=".Anticipation" >
+    
+    <view
+        class="com.example.android.anticipation.AnticiButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="AnticiButton"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml b/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/Anticipation/res/values/strings.xml b/samples/devbytes/animation/Anticipation/res/values/strings.xml
new file mode 100644
index 0000000..9097629
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/values/strings.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="app_name">Anticipation</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="menu_settings">Settings</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/Anticipation/res/values/styles.xml b/samples/devbytes/animation/Anticipation/res/values/styles.xml
new file mode 100644
index 0000000..27658b7
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java
new file mode 100644
index 0000000..707765b
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java
@@ -0,0 +1,230 @@
+/*
+ * 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.example.android.anticipation;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Button;
+
+/**
+ * Custom button which can be deformed by skewing the top left and right, to simulate
+ * anticipation and follow-through animation effects. Clicking on the button runs
+ * an animation which moves the button left or right, applying the skew effect to the
+ * button. The logic of drawing the button with a skew transform is handled in the
+ * draw() override.
+ */
+public class AnticiButton extends Button {
+
+    private static final LinearInterpolator sLinearInterpolator = new LinearInterpolator();
+    private static final DecelerateInterpolator sDecelerator = new DecelerateInterpolator(8);
+    private static final AccelerateInterpolator sAccelerator = new AccelerateInterpolator();
+    private static final OvershootInterpolator sOvershooter = new OvershootInterpolator();
+    private static final DecelerateInterpolator sQuickDecelerator = new DecelerateInterpolator();
+    
+    private float mSkewX = 0;
+    ObjectAnimator downAnim = null;
+    boolean mOnLeft = true;
+    RectF mTempRect = new RectF();
+    
+    public AnticiButton(Context context) {
+        super(context);
+        init();
+    }
+
+    public AnticiButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    public AnticiButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        setOnTouchListener(mTouchListener);
+        setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                runClickAnim();
+            }
+        });
+    }
+
+    /**
+     * The skew effect is handled by changing the transform of the Canvas
+     * and then calling the usual superclass draw() method.
+     */
+    @Override
+    public void draw(Canvas canvas) {
+        if (mSkewX != 0) {
+            canvas.translate(0, getHeight());
+            canvas.skew(mSkewX, 0);
+            canvas.translate(0,  -getHeight());
+        }
+        super.draw(canvas);
+    }
+
+    /**
+     * Anticipate the future animation by rearing back, away from the direction of travel
+     */
+    private void runPressAnim() {
+        downAnim = ObjectAnimator.ofFloat(this, "skewX", mOnLeft ? .5f : -.5f);
+        downAnim.setDuration(2500);
+        downAnim.setInterpolator(sDecelerator);
+        downAnim.start();
+    }
+
+    /**
+     * Finish the "anticipation" animation (skew the button back from the direction of
+     * travel), animate it to the other side of the screen, then un-skew the button
+     * with an Overshoot effect.
+     */
+    private void runClickAnim() {
+        // Anticipation
+        ObjectAnimator finishDownAnim = null;
+        if (downAnim != null && downAnim.isRunning()) {
+            // finish the skew animation quickly
+            downAnim.cancel();
+            finishDownAnim = ObjectAnimator.ofFloat(this, "skewX",
+                    mOnLeft ? .5f : -.5f);
+            finishDownAnim.setDuration(150);
+            finishDownAnim.setInterpolator(sQuickDecelerator);
+        }
+        
+        // Slide. Use LinearInterpolator in this rare situation where we want to start
+        // and end fast (no acceleration or deceleration, since we're doing that part
+        // during the anticipation and overshoot phases).
+        ObjectAnimator moveAnim = ObjectAnimator.ofFloat(this,
+                View.TRANSLATION_X, mOnLeft ? 400 : 0);
+        moveAnim.setInterpolator(sLinearInterpolator);
+        moveAnim.setDuration(150);
+        
+        // Then overshoot by stopping the movement but skewing the button as if it couldn't
+        // all stop at once
+        ObjectAnimator skewAnim = ObjectAnimator.ofFloat(this, "skewX",
+                mOnLeft ? -.5f : .5f);
+        skewAnim.setInterpolator(sQuickDecelerator);
+        skewAnim.setDuration(100);
+        // and wobble it
+        ObjectAnimator wobbleAnim = ObjectAnimator.ofFloat(this, "skewX", 0);
+        wobbleAnim.setInterpolator(sOvershooter);
+        wobbleAnim.setDuration(150);
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(moveAnim, skewAnim, wobbleAnim);
+        if (finishDownAnim != null) {
+            set.play(finishDownAnim).before(moveAnim);
+        }
+        set.start();
+        mOnLeft = !mOnLeft;
+    }
+
+    /**
+     * Restore the button to its un-pressed state
+     */
+    private void runCancelAnim() {
+        if (downAnim != null && downAnim.isRunning()) {
+            downAnim.cancel();
+            ObjectAnimator reverser = ObjectAnimator.ofFloat(this, "skewX", 0);
+            reverser.setDuration(200);
+            reverser.setInterpolator(sAccelerator);
+            reverser.start();
+            downAnim = null;
+        }
+    }
+
+    /**
+     * Handle touch events directly since we want to react on down/up events, not just
+     * button clicks
+     */
+    private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+        
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+                if (isPressed()) {
+                    performClick();
+                    setPressed(false);
+                    break;
+                }
+                // No click: Fall through; equivalent to cancel event
+            case MotionEvent.ACTION_CANCEL:
+                // Run the cancel animation in either case
+                runCancelAnim();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float x = event.getX();
+                float y = event.getY();
+                boolean isInside = (x > 0 && x < getWidth() &&
+                        y > 0 && y < getHeight());
+                if (isPressed() != isInside) {
+                    setPressed(isInside);
+                }
+                break;
+            case MotionEvent.ACTION_DOWN:
+                setPressed(true);
+                runPressAnim();
+                break;
+            default:
+                break;
+            }
+            return true;
+        }
+    };
+    
+    public float getSkewX() {
+        return mSkewX;
+    }
+    
+    /**
+     * Sets the amount of left/right skew on the button, which determines how far the button
+     * leans.
+     */
+    public void setSkewX(float value) {
+        if (value != mSkewX) {
+            mSkewX = value;
+            invalidate();             // force button to redraw with new skew value
+            invalidateSkewedBounds(); // also invalidate appropriate area of parent
+        }
+    }
+    
+    /**
+     * Need to invalidate proper area of parent for skewed bounds
+     */
+    private void invalidateSkewedBounds() {
+        if (mSkewX != 0) {
+            Matrix matrix = new Matrix();
+            matrix.setSkew(-mSkewX, 0);
+            mTempRect.set(0, 0, getRight(), getBottom());
+            matrix.mapRect(mTempRect);
+            mTempRect.offset(getLeft() + getTranslationX(), getTop() + getTranslationY());
+            ((View) getParent()).invalidate((int) mTempRect.left, (int) mTempRect.top,
+                    (int) (mTempRect.right +.5f), (int) (mTempRect.bottom + .5f));
+        }
+    }
+}
diff --git a/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java
new file mode 100644
index 0000000..4ef8f62
--- /dev/null
+++ b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java
@@ -0,0 +1,40 @@
+/*
+ * 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.example.android.anticipation;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * This example shows how to animate some simple deformations on a standard UI widget
+ * to achieve some interactive and cartoon-ish effects.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class Anticipation extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+}
diff --git a/samples/devbytes/animation/Bouncer/AndroidManifest.xml b/samples/devbytes/animation/Bouncer/AndroidManifest.xml
new file mode 100644
index 0000000..ff10b72
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/AndroidManifest.xml
@@ -0,0 +1,67 @@
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.bouncer"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".Bouncer"
+            android:label="@string/title_activity_bouncer" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".Bouncer1"
+            android:label="@string/title_activity_bouncer1" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+                <activity
+            android:name=".Bouncer2"
+            android:label="@string/title_activity_bouncer2" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+                <activity
+            android:name="com.example.android.bouncer.Bouncer3"
+            android:label="@string/title_activity_bouncer3" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+            </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/Bouncer/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/Bouncer/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Bouncer/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/Bouncer/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Bouncer/res/drawable-nodpi/electricsheep.png b/samples/devbytes/animation/Bouncer/res/drawable-nodpi/electricsheep.png
new file mode 100644
index 0000000..22b224a
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/drawable-nodpi/electricsheep.png
Binary files differ
diff --git a/samples/devbytes/animation/Bouncer/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/Bouncer/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer.xml b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer.xml
new file mode 100644
index 0000000..543951b
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer.xml
@@ -0,0 +1,28 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:background="#007F00"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <view android:id="@+id/myview"
+        class="com.example.android.bouncer.Bouncer$MyView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".Bouncer" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer1.xml b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer1.xml
new file mode 100644
index 0000000..765f6ae
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer1.xml
@@ -0,0 +1,28 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:background="#007F00"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <view android:id="@+id/myview"
+        class="com.example.android.bouncer.Bouncer1$MyView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".Bouncer" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer2.xml b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer2.xml
new file mode 100644
index 0000000..db21eee
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer2.xml
@@ -0,0 +1,28 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:background="#007F00"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <view android:id="@+id/myview"
+        class="com.example.android.bouncer.Bouncer2$MyView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".Bouncer" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer3.xml b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer3.xml
new file mode 100644
index 0000000..a1a6911
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/layout/activity_bouncer3.xml
@@ -0,0 +1,28 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:background="#007F00"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <view android:id="@+id/myview"
+        class="com.example.android.bouncer.Bouncer3$MyView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".Bouncer" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/Bouncer/res/values-v11/styles.xml b/samples/devbytes/animation/Bouncer/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/Bouncer/res/values-v14/styles.xml b/samples/devbytes/animation/Bouncer/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/Bouncer/res/values/strings.xml b/samples/devbytes/animation/Bouncer/res/values/strings.xml
new file mode 100644
index 0000000..85e2391
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/values/strings.xml
@@ -0,0 +1,23 @@
+<!-- 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="app_name">Bouncer</string>
+    <string name="title_activity_bouncer">Bouncer</string>
+    <string name="title_activity_bouncer1">Bouncer1</string>
+    <string name="title_activity_bouncer2">Bouncer2</string>
+    <string name="title_activity_bouncer3">Bouncer3</string>
+
+</resources>
diff --git a/samples/devbytes/animation/Bouncer/res/values/styles.xml b/samples/devbytes/animation/Bouncer/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer.java b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer.java
new file mode 100644
index 0000000..a04c378
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer.java
@@ -0,0 +1,144 @@
+/*
+ * 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.example.android.bouncer;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This example shows simple uses of ValueAnimator, ObjectAnimator, and interpolators
+ * to control how a shape is moved around on the screen.
+ *
+ * The Bouncer1, Bouncer2, and Vouncer3 files are exactly like this one except for variations
+ * in how the animation is set up and run.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=vCTcmPIKgpM.
+ */
+public class Bouncer extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bouncer);
+    }
+
+    /**
+     * A custom view is used to paint the green background and the shape.
+     */
+    static class MyView extends View {
+
+        Bitmap mBitmap;
+        Paint paint = new Paint();
+        int mShapeX, mShapeY;
+        int mShapeW, mShapeH;
+
+        public MyView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            setupShape();
+        }
+
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setupShape();
+        }
+
+        public MyView(Context context) {
+            super(context);
+            setupShape();
+        }
+
+        private void setupShape() {
+            mBitmap = BitmapFactory.decodeResource(getResources(),
+                    R.drawable.electricsheep);
+            mShapeW = mBitmap.getWidth();
+            mShapeH = mBitmap.getHeight();
+            setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startAnimation();
+                }
+            });
+        }
+
+        /**
+         * Moving the shape in x or y causes an invalidation of the area it used to occupy
+         * plus the area it now occupies.
+         */
+        public void setShapeX(int shapeX) {
+            int minX = mShapeX;
+            int maxX = mShapeX + mShapeW;
+            mShapeX = shapeX;
+            minX = Math.min(mShapeX, minX);
+            maxX = Math.max(mShapeX + mShapeW, maxX);
+            invalidate(minX, mShapeY, maxX, mShapeY + mShapeH);
+        }
+
+        /**
+         * Moving the shape in x or y causes an invalidation of the area it used to occupy
+         * plus the area it now occupies.
+         */
+        public void setShapeY(int shapeY) {
+            int minY = mShapeY;
+            int maxY = mShapeY + mShapeH;
+            mShapeY = shapeY;
+            minY = Math.min(mShapeY, minY);
+            maxY = Math.max(mShapeY + mShapeH, maxY);
+            invalidate(mShapeX, minY, mShapeX + mShapeW, maxY);
+        }
+
+        @Override
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            mShapeX = (w - mBitmap.getWidth()) / 2;
+            mShapeY = 0;
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, mShapeX, mShapeY, paint);
+        }
+
+        void startAnimation() {
+            // This version uses ValueAnimator, which requires adding an update
+            // listener and manually updating the shape position on each frame.
+            ValueAnimator anim = getValueAnimator();
+            anim.start();
+        }
+
+        ValueAnimator getValueAnimator() {
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    setShapeY((int) (animation.getAnimatedFraction() *
+                            (getHeight() - mShapeH)));
+                }
+            });
+            return anim;
+        }
+
+    }
+}
diff --git a/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer1.java b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer1.java
new file mode 100644
index 0000000..554c0f2
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer1.java
@@ -0,0 +1,128 @@
+/*
+ * 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.example.android.bouncer;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * See the comments in Bouncer for the overall functionality of this app. Changes for this
+ * variation are down in the animation setup code.
+ */
+public class Bouncer1 extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bouncer1);
+    }
+
+    static class MyView extends View {
+
+        Bitmap mBitmap;
+        Paint paint = new Paint();
+        int mShapeX, mShapeY;
+        int mShapeW, mShapeH;
+
+        public MyView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            setupShape();
+        }
+
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setupShape();
+        }
+
+        public MyView(Context context) {
+            super(context);
+            setupShape();
+        }
+
+        public void setShapeX(int shapeX) {
+            int minX = mShapeX;
+            int maxX = mShapeX + mShapeW;
+            mShapeX = shapeX;
+            minX = Math.min(mShapeX, minX);
+            maxX = Math.max(mShapeX + mShapeW, maxX);
+            invalidate(minX, mShapeY, maxX, mShapeY + mShapeH);
+        }
+
+        public void setShapeY(int shapeY) {
+            int minY = mShapeY;
+            int maxY = mShapeY + mShapeH;
+            mShapeY = shapeY;
+            minY = Math.min(mShapeY, minY);
+            maxY = Math.max(mShapeY + mShapeH, maxY);
+            invalidate(mShapeX, minY, mShapeX + mShapeW, maxY);
+        }
+
+        private void setupShape() {
+            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.electricsheep);
+            mShapeW = mBitmap.getWidth();
+            mShapeH = mBitmap.getHeight();
+            setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startAnimation();
+                }
+            });
+        }
+
+        @Override
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            mShapeX = (w - mBitmap.getWidth()) / 2;
+            mShapeY = 0;
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, mShapeX, mShapeY, paint);
+        }
+
+        void startAnimation() {
+            ValueAnimator anim = getValueAnimator();
+            // In this variation, we put the shape into an infinite bounce, where it keeps moving
+            // up and down. Note that it's still not actually "bouncing" because it just uses
+            // default time interpolation.
+            anim.setRepeatCount(ValueAnimator.INFINITE);
+            anim.setRepeatMode(ValueAnimator.REVERSE);
+            anim.start();
+        }
+
+        ValueAnimator getValueAnimator() {
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    setShapeY((int) (animation.getAnimatedFraction() * (getHeight() - mShapeH)));
+                }
+            });
+            return anim;
+        }
+
+    }
+}
diff --git a/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer2.java b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer2.java
new file mode 100644
index 0000000..30ff64d
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer2.java
@@ -0,0 +1,130 @@
+/*
+ * 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.example.android.bouncer;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+
+/**
+ * See the comments in Bouncer for the overall functionality of this app. Changes for this
+ * variation are down in the animation setup code.
+ */
+public class Bouncer2 extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bouncer2);
+    }
+
+    static class MyView extends View {
+
+        Bitmap mBitmap;
+        Paint paint = new Paint();
+        int mShapeX, mShapeY;
+        int mShapeW, mShapeH;
+
+        public MyView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            setupShape();
+        }
+
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setupShape();
+        }
+
+        public MyView(Context context) {
+            super(context);
+            setupShape();
+        }
+
+        public void setShapeX(int shapeX) {
+            int minX = mShapeX;
+            int maxX = mShapeX + mShapeW;
+            mShapeX = shapeX;
+            minX = Math.min(mShapeX, minX);
+            maxX = Math.max(mShapeX + mShapeW, maxX);
+            invalidate(minX, mShapeY, maxX, mShapeY + mShapeH);
+        }
+
+        public void setShapeY(int shapeY) {
+            int minY = mShapeY;
+            int maxY = mShapeY + mShapeH;
+            mShapeY = shapeY;
+            minY = Math.min(mShapeY, minY);
+            maxY = Math.max(mShapeY + mShapeH, maxY);
+            invalidate(mShapeX, minY, mShapeX + mShapeW, maxY);
+        }
+
+        private void setupShape() {
+            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.electricsheep);
+            mShapeW = mBitmap.getWidth();
+            mShapeH = mBitmap.getHeight();
+            setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startAnimation();
+                }
+            });
+        }
+
+        @Override
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            mShapeX = (w - mBitmap.getWidth()) / 2;
+            mShapeY = 0;
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, mShapeX, mShapeY, paint);
+        }
+
+        void startAnimation() {
+            ValueAnimator anim = getValueAnimator();
+            anim.setRepeatCount(ValueAnimator.INFINITE);
+            anim.setRepeatMode(ValueAnimator.REVERSE);
+            // This variation finally has the shape bouncing, due to the use of an
+            // AccelerateInterpolator, which speeds up as the animation proceed. Note that
+            // when the animation reverses, the interpolator acts in reverse, decelerating
+            // movement.
+            anim.setInterpolator(new AccelerateInterpolator());
+            anim.start();
+        }
+
+        ValueAnimator getValueAnimator() {
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    setShapeY((int) (animation.getAnimatedFraction() * (getHeight() - mShapeH)));
+                }
+            });
+            return anim;
+        }
+    }
+}
diff --git a/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer3.java b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer3.java
new file mode 100644
index 0000000..cea9a3d
--- /dev/null
+++ b/samples/devbytes/animation/Bouncer/src/com/example/android/bouncer/Bouncer3.java
@@ -0,0 +1,124 @@
+/*
+ * 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.example.android.bouncer;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+
+/**
+ * See the comments in Bouncer for the overall functionality of this app. Changes for this
+ * variation are down in the animation setup code.
+ */
+public class Bouncer3 extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bouncer3);
+    }
+
+    static class MyView extends View {
+
+        Bitmap mBitmap;
+        Paint paint = new Paint();
+        int mShapeX, mShapeY;
+        int mShapeW, mShapeH;
+
+        public MyView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            setupShape();
+        }
+
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setupShape();
+        }
+
+        public MyView(Context context) {
+            super(context);
+            setupShape();
+        }
+
+        public void setShapeX(int shapeX) {
+            int minX = mShapeX;
+            int maxX = mShapeX + mShapeW;
+            mShapeX = shapeX;
+            minX = Math.min(mShapeX, minX);
+            maxX = Math.max(mShapeX + mShapeW, maxX);
+            invalidate(minX, mShapeY, maxX, mShapeY + mShapeH);
+        }
+
+        public void setShapeY(int shapeY) {
+            int minY = mShapeY;
+            int maxY = mShapeY + mShapeH;
+            mShapeY = shapeY;
+            minY = Math.min(mShapeY, minY);
+            maxY = Math.max(mShapeY + mShapeH, maxY);
+            invalidate(mShapeX, minY, mShapeX + mShapeW, maxY);
+        }
+
+        private void setupShape() {
+            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.electricsheep);
+            mShapeW = mBitmap.getWidth();
+            mShapeH = mBitmap.getHeight();
+            setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startAnimation();
+                }
+            });
+        }
+
+        @Override
+        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+            mShapeX = (w - mBitmap.getWidth()) / 2;
+            mShapeY = 0;
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.drawBitmap(mBitmap, mShapeX, mShapeY, paint);
+        }
+
+        void startAnimation() {
+            // This variation uses an ObjectAnimator. The functionality is exactly the same as
+            // in Bouncer2, but this time the boilerplate code is greatly reduced because we
+            // tell ObjectAnimator to automatically animate the target object for us, so we no
+            // longer need to listen for frame updates and do that work ourselves.
+            ObjectAnimator anim = getObjectAnimator();
+            anim.setRepeatCount(ValueAnimator.INFINITE);
+            anim.setRepeatMode(ValueAnimator.REVERSE);
+            anim.setInterpolator(new AccelerateInterpolator());
+            anim.start();
+        }
+
+        ObjectAnimator getObjectAnimator() {
+            return ObjectAnimator.ofInt(this, "shapeY", 0, (getHeight() - mShapeH));
+        }
+
+    }
+}
diff --git a/samples/devbytes/animation/CrossFading/AndroidManifest.xml b/samples/devbytes/animation/CrossFading/AndroidManifest.xml
new file mode 100644
index 0000000..0d38d6a
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.crossfading"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.crossfading.CrossFading"
+            android:label="@string/title_activity_cross_fading" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/CrossFading/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/CrossFading/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CrossFading/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/CrossFading/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CrossFading/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/CrossFading/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CrossFading/res/layout/activity_cross_fading.xml b/samples/devbytes/animation/CrossFading/res/layout/activity_cross_fading.xml
new file mode 100644
index 0000000..9c5423d
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/layout/activity_cross_fading.xml
@@ -0,0 +1,27 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <ImageView
+        android:id="@+id/imageview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/CrossFading/res/values-v11/styles.xml b/samples/devbytes/animation/CrossFading/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/CrossFading/res/values-v14/styles.xml b/samples/devbytes/animation/CrossFading/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/CrossFading/res/values/strings.xml b/samples/devbytes/animation/CrossFading/res/values/strings.xml
new file mode 100644
index 0000000..dbfca7e
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!-- 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="app_name">CrossFading</string>
+    <string name="title_activity_cross_fading">CrossFading</string>
+
+</resources>
diff --git a/samples/devbytes/animation/CrossFading/res/values/styles.xml b/samples/devbytes/animation/CrossFading/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/CrossFading/src/com/example/android/crossfading/CrossFading.java b/samples/devbytes/animation/CrossFading/src/com/example/android/crossfading/CrossFading.java
new file mode 100644
index 0000000..e02694c
--- /dev/null
+++ b/samples/devbytes/animation/CrossFading/src/com/example/android/crossfading/CrossFading.java
@@ -0,0 +1,81 @@
+/*
+ * 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.example.android.crossfading;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * This example shows how to use TransitionDrawable to perform a simple cross-fade effect
+ * between two drawables.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=atH3o2uh_94.
+ */
+public class CrossFading extends Activity {
+
+    int mCurrentDrawable = 0;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_cross_fading);
+
+        final ImageView imageview = (ImageView) findViewById(R.id.imageview);
+
+        // Create red and green bitmaps to cross-fade between
+        Bitmap bitmap0 = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
+        Bitmap bitmap1 = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap0);
+        canvas.drawColor(Color.RED);
+        canvas = new Canvas(bitmap1);
+        canvas.drawColor(Color.GREEN);
+        BitmapDrawable drawables[] = new BitmapDrawable[2];
+        drawables[0] = new BitmapDrawable(getResources(), bitmap0);
+        drawables[1] = new BitmapDrawable(getResources(), bitmap1);
+
+        // Add the red/green bitmap drawables to a TransitionDrawable. They are layered
+        // in the transition drawalbe. The cross-fade effect happens by fading one out and the
+        // other in.
+        final TransitionDrawable crossfader = new TransitionDrawable(drawables);
+        imageview.setImageDrawable(crossfader);
+
+        // Clicking on the drawable will cause the cross-fade effect to run. Depending on
+        // which drawable is currently being shown, we either 'start' or 'reverse' the
+        // transition, which determines which drawable is faded out/in during the transition.
+        imageview.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (mCurrentDrawable == 0) {
+                    crossfader.startTransition(500);
+                    mCurrentDrawable = 1;
+                } else {
+                    crossfader.reverseTransition(500);
+                    mCurrentDrawable = 0;
+                }
+            }
+        });
+    }
+}
diff --git a/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml b/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml
new file mode 100644
index 0000000..195faae
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.curvedmotion"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="11"
+        android:targetSdkVersion="11" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.curvedmotion.CurvedMotion"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml b/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml
new file mode 100644
index 0000000..10adea3
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml
@@ -0,0 +1,33 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".CurvedMotion" >
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:id="@+id/button"
+        android:text="Click Me!" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/values-v11/styles.xml b/samples/devbytes/animation/CurvedMotion/res/values-v11/styles.xml
new file mode 100644
index 0000000..556d2da
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/values-v11/styles.xml
@@ -0,0 +1,25 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/values-v14/styles.xml b/samples/devbytes/animation/CurvedMotion/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/values/dimens.xml b/samples/devbytes/animation/CurvedMotion/res/values/dimens.xml
new file mode 100644
index 0000000..90db76b
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!-- 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>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/values/strings.xml b/samples/devbytes/animation/CurvedMotion/res/values/strings.xml
new file mode 100644
index 0000000..a070e15
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/values/strings.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="app_name">CurvedMotion</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Hello world!</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/res/values/styles.xml b/samples/devbytes/animation/CurvedMotion/res/values/styles.xml
new file mode 100644
index 0000000..27658b7
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/AnimatorPath.java b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/AnimatorPath.java
new file mode 100644
index 0000000..d030c6a
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/AnimatorPath.java
@@ -0,0 +1,67 @@
+/*
+ * 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.example.android.curvedmotion;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A simple Path object that holds information about the points along
+ * a path. The API allows you to specify a move location (which essentially
+ * jumps from the previous point in the path to the new one), a line location
+ * (which creates a line segment from the previous location) and a curve
+ * location (which creates a B�zier curve from the previous location).
+ */
+public class AnimatorPath {
+    
+    // The points in the path
+    ArrayList<PathPoint> mPoints = new ArrayList<PathPoint>();
+
+
+    /**
+     * Move from the current path point to the new one
+     * specified by x and y. This will create a discontinuity if this point is
+     * neither the first point in the path nor the same as the previous point
+     * in the path.
+     */
+    public void moveTo(float x, float y) {
+        mPoints.add(PathPoint.moveTo(x, y));
+    }
+
+    /**
+     * Create a straight line from the current path point to the new one
+     * specified by x and y.
+     */
+    public void lineTo(float x, float y) {
+        mPoints.add(PathPoint.lineTo(x, y));
+    }
+
+    /**
+     * Create a quadratic B�zier curve from the current path point to the new one
+     * specified by x and y. The curve uses the current path location as the first anchor
+     * point, the control points (c0X, c0Y) and (c1X, c1Y), and (x, y) as the end anchor.
+     */
+    public void curveTo(float c0X, float c0Y, float c1X, float c1Y, float x, float y) {
+        mPoints.add(PathPoint.curveTo(c0X, c0Y, c1X, c1Y, x, y));
+    }
+
+    /**
+     * Returns a Collection of PathPoint objects that describe all points in the path.
+     */
+    public Collection<PathPoint> getPoints() {
+        return mPoints;
+    }
+}
diff --git a/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/CurvedMotion.java b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/CurvedMotion.java
new file mode 100644
index 0000000..357d72d
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/CurvedMotion.java
@@ -0,0 +1,124 @@
+/*
+ * 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.example.android.curvedmotion;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+
+/**
+ * This app shows how to move a view in a curved path between two endpoints.
+ * The real work is done by PathEvaluator, which interpolates along a path
+ * using Bezier control and anchor points in the path.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class CurvedMotion extends Activity {
+
+    private static final DecelerateInterpolator sDecelerateInterpolator =
+            new DecelerateInterpolator();
+    boolean mTopLeft = true;
+    Button mButton;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_curved_motion);
+        
+        mButton = (Button) findViewById(R.id.button);
+        mButton.setOnClickListener(new View.OnClickListener() {
+            
+            @Override
+            public void onClick(View v) {
+                // Capture current location of button
+                final int oldLeft = mButton.getLeft();
+                final int oldTop = mButton.getTop();
+                
+                // Change layout parameters of button to move it
+                moveButton();
+                
+                // Add OnPreDrawListener to catch button after layout but before drawing
+                mButton.getViewTreeObserver().addOnPreDrawListener(
+                        new ViewTreeObserver.OnPreDrawListener() {
+                            public boolean onPreDraw() {
+                                mButton.getViewTreeObserver().removeOnPreDrawListener(this);
+                                
+                                // Capture new location
+                                int left = mButton.getLeft();
+                                int top = mButton.getTop();
+                                int deltaX = left - oldLeft;
+                                int deltaY = top - oldTop;
+
+                                // Set up path to new location using a B�zier spline curve
+                                AnimatorPath path = new AnimatorPath();
+                                path.moveTo(-deltaX, -deltaY);
+                                path.curveTo(-(deltaX/2), -deltaY, 0, -deltaY/2, 0, 0);
+                                
+                                // Set up the animation
+                                final ObjectAnimator anim = ObjectAnimator.ofObject(
+                                        CurvedMotion.this, "buttonLoc", 
+                                        new PathEvaluator(), path.getPoints().toArray());
+                                anim.setInterpolator(sDecelerateInterpolator);
+                                anim.start();
+                                return true;
+                            }
+                        });
+            }
+        });
+    }
+    
+    /**
+     * Toggles button location on click between top-left and bottom-right
+     */
+    private void moveButton() {
+        LayoutParams params = (LayoutParams) mButton.getLayoutParams();
+        if (mTopLeft) {
+            params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
+            params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
+            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+            params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+        } else {
+            params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+            params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+            params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+            params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+        }
+        mButton.setLayoutParams(params);
+        mTopLeft = !mTopLeft;
+    }
+
+    /**
+     * We need this setter to translate between the information the animator
+     * produces (a new "PathPoint" describing the current animated location)
+     * and the information that the button requires (an xy location). The
+     * setter will be called by the ObjectAnimator given the 'buttonLoc'
+     * property string.
+     */
+    public void setButtonLoc(PathPoint newLoc) {
+        mButton.setTranslationX(newLoc.mX);
+        mButton.setTranslationY(newLoc.mY);
+    }
+
+}
diff --git a/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathEvaluator.java b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathEvaluator.java
new file mode 100644
index 0000000..e68d4cd
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathEvaluator.java
@@ -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.
+ */
+package com.example.android.curvedmotion;
+
+import android.animation.TypeEvaluator;
+
+/**
+ * This evaluator interpolates between two PathPoint values given the value t, the
+ * proportion traveled between those points. The value of the interpolation depends
+ * on the operation specified by the endValue (the operation for the interval between
+ * PathPoints is always specified by the end point of that interval).
+ */
+public class PathEvaluator implements TypeEvaluator<PathPoint> {
+    @Override
+    public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) {
+        float x, y;
+        if (endValue.mOperation == PathPoint.CURVE) {
+            float oneMinusT = 1 - t;
+            x = oneMinusT * oneMinusT * oneMinusT * startValue.mX +
+                    3 * oneMinusT * oneMinusT * t * endValue.mControl0X +
+                    3 * oneMinusT * t * t * endValue.mControl1X +
+                    t * t * t * endValue.mX;
+            y = oneMinusT * oneMinusT * oneMinusT * startValue.mY +
+                    3 * oneMinusT * oneMinusT * t * endValue.mControl0Y +
+                    3 * oneMinusT * t * t * endValue.mControl1Y +
+                    t * t * t * endValue.mY;
+        } else if (endValue.mOperation == PathPoint.LINE) {
+            x = startValue.mX + t * (endValue.mX - startValue.mX);
+            y = startValue.mY + t * (endValue.mY - startValue.mY);
+        } else {
+            x = endValue.mX;
+            y = endValue.mY;
+        }
+        return PathPoint.moveTo(x, y);
+    }
+}
diff --git a/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathPoint.java b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathPoint.java
new file mode 100644
index 0000000..ba1a05e
--- /dev/null
+++ b/samples/devbytes/animation/CurvedMotion/src/com/example/android/curvedmotion/PathPoint.java
@@ -0,0 +1,101 @@
+/*
+ * 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.example.android.curvedmotion;
+
+/**
+ * A class that holds information about a location and how the path should get to that
+ * location from the previous path location (if any). Any PathPoint holds the information for
+ * its location as well as the instructions on how to traverse the preceding interval from the
+ * previous location.
+ */
+public class PathPoint {
+
+    /**
+     * The possible path operations that describe how to move from a preceding PathPoint to the
+     * location described by this PathPoint.
+     */
+    public static final int MOVE = 0;
+    public static final int LINE = 1;
+    public static final int CURVE = 2;
+
+    /**
+     * The location of this PathPoint
+     */
+    float mX, mY;
+    
+    /**
+     * The first control point, if any, for a PathPoint of type CURVE
+     */
+    float mControl0X, mControl0Y;
+
+    /**
+     * The second control point, if any, for a PathPoint of type CURVE
+     */
+    float mControl1X, mControl1Y;
+
+    /**
+     * The motion described by the path to get from the previous PathPoint in an AnimatorPath
+     * to the location of this PathPoint. This can be one of MOVE, LINE, or CURVE.
+     */
+    int mOperation;
+
+
+
+    /**
+     * Line/Move constructor
+     */
+    private PathPoint(int operation, float x, float y) {
+        mOperation = operation;
+        mX = x;
+        mY = y;
+    }
+
+    /**
+     * Curve constructor
+     */
+    private PathPoint(float c0X, float c0Y, float c1X, float c1Y, float x, float y) {
+        mControl0X = c0X;
+        mControl0Y = c0Y;
+        mControl1X = c1X;
+        mControl1Y = c1Y;
+        mX = x;
+        mY = y;
+        mOperation = CURVE;
+    }
+
+    /**
+     * Constructs and returns a PathPoint object that describes a line to the given xy location.
+     */
+    public static PathPoint lineTo(float x, float y) {
+        return new PathPoint(LINE, x, y);
+    }
+
+    /**
+     * Constructs and returns a PathPoint object that describes a curve to the given xy location
+     * with the control points at c0 and c1.
+     */
+    public static PathPoint curveTo(float c0X, float c0Y, float c1X, float c1Y, float x, float y) {
+        return new PathPoint(c0X,  c0Y, c1X, c1Y, x, y);
+    }
+    
+    /**
+     * Constructs and returns a PathPoint object that describes a discontinuous move to the given
+     * xy location.
+     */
+    public static PathPoint moveTo(float x, float y) {
+        return new PathPoint(MOVE, x, y);
+    }
+}
diff --git a/samples/devbytes/animation/KeyframeAnimation/AndroidManifest.xml b/samples/devbytes/animation/KeyframeAnimation/AndroidManifest.xml
new file mode 100644
index 0000000..2406281
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.keyframeanimation"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.keyframeanimation.KeyframeAnimation"
+            android:label="@string/title_activity_keyframe_animation" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/KeyframeAnimation/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/KeyframeAnimation/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/KeyframeAnimation/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/layout/activity_keyframe_animation.xml b/samples/devbytes/animation/KeyframeAnimation/res/layout/activity_keyframe_animation.xml
new file mode 100644
index 0000000..4da3948
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/layout/activity_keyframe_animation.xml
@@ -0,0 +1,28 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <ImageView
+        android:id="@+id/imageview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        tools:context=".KeyframeAnimation" />
+
+</RelativeLayout>
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/values-v11/styles.xml b/samples/devbytes/animation/KeyframeAnimation/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/values-v14/styles.xml b/samples/devbytes/animation/KeyframeAnimation/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/values/strings.xml b/samples/devbytes/animation/KeyframeAnimation/res/values/strings.xml
new file mode 100644
index 0000000..330dae2
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!-- 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="app_name">KeyframeAnimation</string>
+    <string name="title_activity_keyframe_animation">KeyframeAnimation</string>
+
+</resources>
diff --git a/samples/devbytes/animation/KeyframeAnimation/res/values/styles.xml b/samples/devbytes/animation/KeyframeAnimation/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/KeyframeAnimation/src/com/example/android/keyframeanimation/KeyframeAnimation.java b/samples/devbytes/animation/KeyframeAnimation/src/com/example/android/keyframeanimation/KeyframeAnimation.java
new file mode 100644
index 0000000..9a6bbd0
--- /dev/null
+++ b/samples/devbytes/animation/KeyframeAnimation/src/com/example/android/keyframeanimation/KeyframeAnimation.java
@@ -0,0 +1,85 @@
+/*
+ * 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.example.android.keyframeanimation;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * This example shows how to use AnimationDrawable to construct a keyframe animation where each
+ * frame is shown for a specified duration.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=V3ksidLf7vA.
+ */
+public class KeyframeAnimation extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_keyframe_animation);
+
+        ImageView imageview = (ImageView) findViewById(R.id.imageview);
+
+        // Create the AnimationDrawable in which we will store all frames of the animation
+        final AnimationDrawable animationDrawable = new AnimationDrawable();
+        for (int i = 0; i < 10; ++i) {
+            animationDrawable.addFrame(getDrawableForFrameNumber(i), 300);
+        }
+        // Run until we say stop
+        animationDrawable.setOneShot(false);
+
+        imageview.setImageDrawable(animationDrawable);
+
+        // When the user clicks on the image, toggle the animation on/off
+        imageview.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                if (animationDrawable.isRunning()) {
+                    animationDrawable.stop();
+                } else {
+                    animationDrawable.start();
+                }
+            }
+        });
+    }
+
+    /**
+     * The 'frames' in this app are nothing more than a gray background with text indicating
+     * the number of the frame.
+     */
+    private BitmapDrawable getDrawableForFrameNumber(int frameNumber) {
+        Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(Color.GRAY);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setTextSize(80);
+        paint.setColor(Color.BLACK);
+        canvas.drawText("Frame " + frameNumber, 40, 220, paint);
+        return new BitmapDrawable(getResources(), bitmap);
+    }
+
+}
diff --git a/samples/devbytes/animation/LayoutTransChanging/AndroidManifest.xml b/samples/devbytes/animation/LayoutTransChanging/AndroidManifest.xml
new file mode 100644
index 0000000..d9b55f5
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.example.android.layouttranschanging"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <uses-sdk android:minSdkVersion="16"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application
+      android:icon="@drawable/icon"
+      android:label="@string/app_name"
+      android:hardwareAccelerated="true">
+        <activity android:label="@string/app_name"
+                  android:name="com.example.android.layouttranschanging.LayoutTransChanging">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/LayoutTransChanging/res/drawable-hdpi/icon.png b/samples/devbytes/animation/LayoutTransChanging/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/samples/devbytes/animation/LayoutTransChanging/res/drawable-ldpi/icon.png b/samples/devbytes/animation/LayoutTransChanging/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/samples/devbytes/animation/LayoutTransChanging/res/drawable-mdpi/icon.png b/samples/devbytes/animation/LayoutTransChanging/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/samples/devbytes/animation/LayoutTransChanging/res/layout/main.xml b/samples/devbytes/animation/LayoutTransChanging/res/layout/main.xml
new file mode 100644
index 0000000..3b5dd0d
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/res/layout/main.xml
@@ -0,0 +1,41 @@
+<?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="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/addButton"
+            android:id="@+id/addButton"/>
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/removeButton"
+            android:id="@+id/removeButton"/>
+    </LinearLayout>
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:animateLayoutChanges="true"
+        android:id="@+id/container"/>
+</LinearLayout>
diff --git a/samples/devbytes/animation/LayoutTransChanging/res/values/strings.xml b/samples/devbytes/animation/LayoutTransChanging/res/values/strings.xml
new file mode 100644
index 0000000..d50ad11
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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="app_name">LayoutTransChanging</string>
+    <string name="addButton">Add Item</string>
+    <string name="removeButton">Remove Item</string>
+</resources>
diff --git a/samples/devbytes/animation/LayoutTransChanging/src/com/example/android/layouttranschanging/LayoutTransChanging.java b/samples/devbytes/animation/LayoutTransChanging/src/com/example/android/layouttranschanging/LayoutTransChanging.java
new file mode 100644
index 0000000..e240330
--- /dev/null
+++ b/samples/devbytes/animation/LayoutTransChanging/src/com/example/android/layouttranschanging/LayoutTransChanging.java
@@ -0,0 +1,120 @@
+/*
+ * 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.example.android.layouttranschanging;
+
+import android.animation.LayoutTransition;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+/**
+ * This example shows how to use LayoutTransition to animate simple changes in a layout
+ * container.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=55wLsaWpQ4g.
+ */
+public class LayoutTransChanging extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        final Button addButton =
+                (Button) findViewById(R.id.addButton);
+        final Button removeButton =
+                (Button) findViewById(R.id.removeButton);
+        final LinearLayout container =
+                (LinearLayout) findViewById(R.id.container);
+        final Context context = this;
+
+        // Start with two views
+        for (int i = 0; i < 2; ++i) {
+            container.addView(new ColoredView(this));
+        }
+
+        addButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // Adding a view will cause a LayoutTransition animation
+                container.addView(new ColoredView(context), 1);
+            }
+        });
+
+        removeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (container.getChildCount() > 0) {
+                    // Removing a view will cause a LayoutTransition animation
+                    container.removeViewAt(Math.min(1, container.getChildCount() - 1));
+                }
+            }
+        });
+
+        // Note that this assumes a LayoutTransition is set on the container, which is the
+        // case here because the container has the attribute "animateLayoutChanges" set to true
+        // in the layout file. You can also call setLayoutTransition(new LayoutTransition()) in
+        // code to set a LayoutTransition on any container.
+        LayoutTransition transition = container.getLayoutTransition();
+
+        // New capability as of Jellybean; monitor the container for *all* layout changes
+        // (not just add/remove/visibility changes) and animate these changes as well.
+        transition.enableTransitionType(LayoutTransition.CHANGING);
+    }
+
+    /**
+     * Custom view painted with a random background color and two different sizes which are
+     * toggled between due to user interaction.
+     */
+    private static class ColoredView extends View {
+
+        private boolean mExpanded = false;
+
+        private LayoutParams mCompressedParams = new LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, 50);
+
+        private LayoutParams mExpandedParams = new LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, 200);
+
+        private ColoredView(Context context) {
+            super(context);
+            int red = (int)(Math.random() * 128 + 127);
+            int green = (int)(Math.random() * 128 + 127);
+            int blue = (int)(Math.random() * 128 + 127);
+            int color = 0xff << 24 | (red << 16) |
+                    (green << 8) | blue;
+            setBackgroundColor(color);
+            setLayoutParams(mCompressedParams);
+            setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    // Size changes will cause a LayoutTransition animation if the CHANGING
+                    // transition is enabled
+                    setLayoutParams(mExpanded ? mCompressedParams : mExpandedParams);
+                    mExpanded = !mExpanded;
+                    requestLayout();
+                }
+            });
+        }
+    }
+}
diff --git a/samples/devbytes/animation/ListViewAnimations/AndroidManifest.xml b/samples/devbytes/animation/ListViewAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..16a0bf1
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.listviewanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.listviewanimations.ListViewAnimations"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/ListViewAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewAnimations/res/layout/activity_list_view_animations.xml b/samples/devbytes/animation/ListViewAnimations/res/layout/activity_list_view_animations.xml
new file mode 100644
index 0000000..b8fc9cd
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/layout/activity_list_view_animations.xml
@@ -0,0 +1,43 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ListViewAnimations" >
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <CheckBox
+            android:id="@+id/vpaCB"
+            android:text="@string/viewpropertyanimator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+        <CheckBox
+            android:id="@+id/setTransientStateCB"
+            android:text="@string/transient_state"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/listview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/ListViewAnimations/res/values-v11/styles.xml b/samples/devbytes/animation/ListViewAnimations/res/values-v11/styles.xml
new file mode 100644
index 0000000..139d283
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/values-v11/styles.xml
@@ -0,0 +1,25 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/ListViewAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..8ac4522
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewAnimations/res/values/strings.xml b/samples/devbytes/animation/ListViewAnimations/res/values/strings.xml
new file mode 100644
index 0000000..5b41f5b
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!-- 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="app_name">ListViewAnimations</string>
+    <string name="viewpropertyanimator">ViewPropertyAnimator</string>
+    <string name="transient_state">Transient State</string>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewAnimations/res/values/styles.xml b/samples/devbytes/animation/ListViewAnimations/res/values/styles.xml
new file mode 100644
index 0000000..f9460a7
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/Cheeses.java b/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/Cheeses.java
new file mode 100644
index 0000000..69c027d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * 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.example.android.listviewanimations;
+
+public class Cheeses {
+
+    public static final String[] sCheeseStrings = {
+            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+    };
+
+}
diff --git a/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/ListViewAnimations.java b/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/ListViewAnimations.java
new file mode 100644
index 0000000..2d2cc68
--- /dev/null
+++ b/samples/devbytes/animation/ListViewAnimations/src/com/example/android/listviewanimations/ListViewAnimations.java
@@ -0,0 +1,134 @@
+/*
+ * 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.example.android.listviewanimations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+
+/**
+ * This example shows how animating ListView items can lead to problems as views are recycled,
+ * and how to perform these types of animations correctly with new API added in Jellybean.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=8MIfSxgsHIs.
+ */
+public class ListViewAnimations extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_view_animations);
+
+        final CheckBox vpaCB = (CheckBox) findViewById(R.id.vpaCB);
+        final CheckBox setTransientStateCB = (CheckBox) findViewById(R.id.setTransientStateCB);
+        final ListView listview = (ListView) findViewById(R.id.listview);
+        final ArrayList<String> cheeseList = new ArrayList<String>();
+        for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
+            cheeseList.add(Cheeses.sCheeseStrings[i]);
+        }
+        final StableArrayAdapter adapter = new StableArrayAdapter(this,
+                android.R.layout.simple_list_item_1, cheeseList);
+        listview.setAdapter(adapter);
+
+        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
+                final String item = (String) parent.getItemAtPosition(position);
+                if (vpaCB.isChecked()) {
+                    view.animate().setDuration(1000).alpha(0).
+                    withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            cheeseList.remove(item);
+                            adapter.notifyDataSetChanged();
+                            view.setAlpha(1);
+                        }
+                    });
+                } else {
+                    // Here's where the problem starts - this animation will animate a View object.
+                    // But that View may get recycled if it is animated out of the container,
+                    // and the animation will continue to fade a view that now contains unrelated
+                    // content.
+                    ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
+                    anim.setDuration(1000);
+                    if (setTransientStateCB.isChecked()) {
+                        // Here's the correct way to do this: if you tell a view that it has
+                        // transientState, then ListView ill avoid recycling it until the
+                        // transientState flag is reset.
+                        // A different approach is to use ViewPropertyAnimator, which sets the
+                        // transientState flag internally.
+                        view.setHasTransientState(true);
+                    }
+                    anim.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            cheeseList.remove(item);
+                            adapter.notifyDataSetChanged();
+                            view.setAlpha(1);
+                            if (setTransientStateCB.isChecked()) {
+                                view.setHasTransientState(false);
+                            }
+                        }
+                    });
+                    anim.start();
+
+                }
+            }
+
+        });
+    }
+
+    private class StableArrayAdapter extends ArrayAdapter<String> {
+
+        HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
+
+        public StableArrayAdapter(Context context, int textViewResourceId,
+                List<String> objects) {
+            super(context, textViewResourceId, objects);
+            for (int i = 0; i < objects.size(); ++i) {
+                mIdMap.put(objects.get(i), i);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            String item = getItem(position);
+            return mIdMap.get(item);
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewItemAnimations/AndroidManifest.xml b/samples/devbytes/animation/ListViewItemAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..173b188
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.listviewitemanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="9"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:theme="@style/WithoutBackground"
+            android:name="com.example.android.listviewitemanimations.ListViewItemAnimations"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/shadowed_background.9.png b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/shadowed_background.9.png
new file mode 100644
index 0000000..6226b9e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/shadowed_background.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/tv_background_with_divider.9.png b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/tv_background_with_divider.9.png
new file mode 100644
index 0000000..e6e3cd5
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/drawable-xhdpi/tv_background_with_divider.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/activity_list_view_item_animations.xml b/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/activity_list_view_item_animations.xml
new file mode 100644
index 0000000..8290927
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/activity_list_view_item_animations.xml
@@ -0,0 +1,37 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ListViewAnimations" >
+
+    <view
+        class="com.example.android.listviewitemanimations.BackgroundContainer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/listViewBackground">
+
+        <ListView
+            android:id="@+id/listview"
+            android:divider="@null"
+            android:dividerHeight="0dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </view>
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/opaque_text_view.xml b/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/opaque_text_view.xml
new file mode 100644
index 0000000..aa03362
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/layout-v9/opaque_text_view.xml
@@ -0,0 +1,25 @@
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:background="@drawable/tv_background_with_divider"
+          android:textAppearance="?android:attr/textAppearanceListItemSmall"
+          android:paddingLeft="10dp"
+          android:paddingRight="10dp"
+          android:textSize="20dp"
+          android:gravity="center_vertical"
+          android:minHeight="48dp"
+        />
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/layout/activity_list_view_item_animations.xml b/samples/devbytes/animation/ListViewItemAnimations/res/layout/activity_list_view_item_animations.xml
new file mode 100644
index 0000000..8290927
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/layout/activity_list_view_item_animations.xml
@@ -0,0 +1,37 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ListViewAnimations" >
+
+    <view
+        class="com.example.android.listviewitemanimations.BackgroundContainer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/listViewBackground">
+
+        <ListView
+            android:id="@+id/listview"
+            android:divider="@null"
+            android:dividerHeight="0dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </view>
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/layout/opaque_text_view.xml b/samples/devbytes/animation/ListViewItemAnimations/res/layout/opaque_text_view.xml
new file mode 100644
index 0000000..c37f62d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/layout/opaque_text_view.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/tv_background_with_divider"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+/>
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/values-v11/styles.xml b/samples/devbytes/animation/ListViewItemAnimations/res/values-v11/styles.xml
new file mode 100644
index 0000000..556d2da
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/values-v11/styles.xml
@@ -0,0 +1,25 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/ListViewItemAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/values/dimens.xml b/samples/devbytes/animation/ListViewItemAnimations/res/values/dimens.xml
new file mode 100644
index 0000000..90db76b
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!-- 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>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/values/strings.xml b/samples/devbytes/animation/ListViewItemAnimations/res/values/strings.xml
new file mode 100644
index 0000000..2cc5f71
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/values/strings.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="app_name">ListViewItemAnimations</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Hello world!</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/res/values/styles.xml b/samples/devbytes/animation/ListViewItemAnimations/res/values/styles.xml
new file mode 100644
index 0000000..ee3c56c
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/res/values/styles.xml
@@ -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.
+-->
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="WithoutBackground" parent="AppTheme">
+        <item name="android:windowBackground">@null</item>
+    </style>
+    
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/BackgroundContainer.java b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/BackgroundContainer.java
new file mode 100644
index 0000000..40ac39a
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/BackgroundContainer.java
@@ -0,0 +1,77 @@
+/*
+ * 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.example.android.listviewitemanimations;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class BackgroundContainer extends FrameLayout {
+
+    boolean mShowing = false;
+    Drawable mShadowedBackground;
+    int mOpenAreaTop, mOpenAreaBottom, mOpenAreaHeight;
+    boolean mUpdateBounds = false;
+    
+    public BackgroundContainer(Context context) {
+        super(context);
+        init();
+    }
+
+    public BackgroundContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public BackgroundContainer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        mShadowedBackground = getContext().getResources().getDrawable(R.drawable.shadowed_background);
+    }
+
+    public void showBackground(int top, int bottom) {
+        setWillNotDraw(false);
+        mOpenAreaTop = top;
+        mOpenAreaHeight = bottom;
+        mShowing = true;
+        mUpdateBounds = true;
+    }
+    
+    public void hideBackground() {
+        setWillNotDraw(true);
+        mShowing = false;
+    }
+    
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mShowing) {
+            if (mUpdateBounds) {
+                mShadowedBackground.setBounds(0, 0, getWidth(), mOpenAreaHeight);
+            }
+            canvas.save();
+            canvas.translate(0, mOpenAreaTop);
+            mShadowedBackground.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/Cheeses.java b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/Cheeses.java
new file mode 100644
index 0000000..5241022
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * 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.example.android.listviewitemanimations;
+
+public class Cheeses {
+
+    public static final String[] sCheeseStrings = {
+            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+    };
+
+}
diff --git a/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/ListViewItemAnimations.java b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/ListViewItemAnimations.java
new file mode 100644
index 0000000..68a7651
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/ListViewItemAnimations.java
@@ -0,0 +1,402 @@
+/*
+ * 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.example.android.listviewitemanimations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationSet;
+import android.view.animation.TranslateAnimation;
+import android.widget.ListView;
+
+/**
+ * This example shows how to use a swipe effect to remove items from a ListView,
+ * and how to use animations to complete the swipe as well as to animate the other
+ * items in the list into their final places. This code works on runtimes back to Gingerbread
+ * (Android 2.3), by using the android.view.animation classes on earlier releases.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class ListViewItemAnimations extends Activity {
+
+    final ArrayList<View> mCheckedViews = new ArrayList<View>();
+    StableArrayAdapter mAdapter;
+    ListView mListView;
+    BackgroundContainer mBackgroundContainer;
+    boolean mSwiping = false;
+    boolean mItemPressed = false;
+    HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>();
+    boolean mAnimating = false;
+    float mCurrentX = 0;
+    float mCurrentAlpha = 1;
+
+    private static final int SWIPE_DURATION = 250;
+    private static final int MOVE_DURATION = 150;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_view_item_animations);
+        
+        mBackgroundContainer = (BackgroundContainer) findViewById(R.id.listViewBackground);
+        mListView = (ListView) findViewById(R.id.listview);
+        final ArrayList<String> cheeseList = new ArrayList<String>();
+        for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
+            cheeseList.add(Cheeses.sCheeseStrings[i]);
+        }
+        mAdapter = new StableArrayAdapter(this,R.layout.opaque_text_view, cheeseList,
+                mTouchListener);
+        mListView.setAdapter(mAdapter);
+    }
+    
+    /**
+     * Returns true if the current runtime is Honeycomb or later
+     */
+    private boolean isRuntimePostGingerbread() {
+        return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB;
+    }
+
+    private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+        
+        float mDownX;
+        private int mSwipeSlop = -1;
+        
+        @SuppressLint("NewApi")
+        @Override
+        public boolean onTouch(final View v, MotionEvent event) {
+            if (mSwipeSlop < 0) {
+                mSwipeSlop = ViewConfiguration.get(ListViewItemAnimations.this).
+                        getScaledTouchSlop();
+            }
+            switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                if (mAnimating) {
+                    // Multi-item swipes not handled
+                    return true;
+                }
+                mItemPressed = true;
+                mDownX = event.getX();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                setSwipePosition(v, 0);
+                mItemPressed = false;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                {
+                    if (mAnimating) {
+                        return true;
+                    }
+                    float x = event.getX();
+                    if (isRuntimePostGingerbread()) {
+                        x += v.getTranslationX();
+                    }
+                    float deltaX = x - mDownX;
+                    float deltaXAbs = Math.abs(deltaX);
+                    if (!mSwiping) {
+                        if (deltaXAbs > mSwipeSlop) {
+                            mSwiping = true;
+                            mListView.requestDisallowInterceptTouchEvent(true);
+                            mBackgroundContainer.showBackground(v.getTop(), v.getHeight());
+                        }
+                    }
+                    if (mSwiping) {
+                        setSwipePosition(v, deltaX);
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                {
+                    if (mAnimating) {
+                        return true;
+                    }
+                    // User let go - figure out whether to animate the view out, or back into place
+                    if (mSwiping) {
+                        float x = event.getX();
+                        if (isRuntimePostGingerbread()) {
+                            x += v.getTranslationX();
+                        }
+                        float deltaX = x - mDownX;
+                        float deltaXAbs = Math.abs(deltaX);
+                        float fractionCovered;
+                        float endX;
+                        final boolean remove;
+                        if (deltaXAbs > v.getWidth() / 4) {
+                            // Greater than a quarter of the width - animate it out
+                            fractionCovered = deltaXAbs / v.getWidth();
+                            endX = deltaX < 0 ? -v.getWidth() : v.getWidth();
+                            remove = true;
+                        } else {
+                            // Not far enough - animate it back
+                            fractionCovered = 1 - (deltaXAbs / v.getWidth());
+                            endX = 0;
+                            remove = false;
+                        }
+                        // Animate position and alpha
+                        long duration = (int) ((1 - fractionCovered) * SWIPE_DURATION);
+                        animateSwipe(v, endX, duration, remove);
+                    } else {
+                        mItemPressed = false;
+                    }
+                }
+                break;
+            default: 
+                return false;
+            }
+            return true;
+        }
+    };
+
+    /**
+     * Animates a swipe of the item either back into place or out of the listview container.
+     * NOTE: This is a simplified version of swipe behavior, for the purposes of this demo
+     * about animation. A real version should use velocity (via the VelocityTracker class)
+     * to send the item off or back at an appropriate speed.
+     */
+    @SuppressLint("NewApi")
+    private void animateSwipe(final View view, float endX, long duration, final boolean remove) {
+        mAnimating = true;
+        mListView.setEnabled(false);
+        if (isRuntimePostGingerbread()) {
+            view.animate().setDuration(duration).
+                    alpha(remove ? 0 : 1).translationX(endX).
+                    setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // Restore animated values
+                            view.setAlpha(1);
+                            view.setTranslationX(0);
+                            if (remove) {
+                                animateOtherViews(mListView, view);
+                            } else {
+                                mBackgroundContainer.hideBackground();
+                                mSwiping = false;
+                                mAnimating = false;
+                                mListView.setEnabled(true);
+                            }
+                            mItemPressed = false;
+                        }
+                    });
+        } else {
+            TranslateAnimation swipeAnim = new TranslateAnimation(mCurrentX, endX, 0, 0);
+            AlphaAnimation alphaAnim = new AlphaAnimation(mCurrentAlpha, remove ? 0 : 1);
+            AnimationSet set = new AnimationSet(true);
+            set.addAnimation(swipeAnim);
+            set.addAnimation(alphaAnim);
+            set.setDuration(duration);
+            view.startAnimation(set);
+            setAnimationEndAction(set, new Runnable() {
+                @Override
+                public void run() {
+                    if (remove) {
+                        animateOtherViews(mListView, view);
+                    } else {
+                        mBackgroundContainer.hideBackground();
+                        mSwiping = false;
+                        mAnimating = false;
+                        mListView.setEnabled(true);
+                    }
+                    mItemPressed = false;
+                }
+            });
+        }
+            
+    }
+        
+    /**
+     * Sets the horizontal position and translucency of the view being swiped.
+     */
+    @SuppressLint("NewApi")
+    private void setSwipePosition(View view, float deltaX) {
+        float fraction = Math.abs(deltaX) / view.getWidth();
+        if (isRuntimePostGingerbread()) {
+            view.setTranslationX(deltaX);
+            view.setAlpha(1 - fraction);
+        } else {
+            // Hello, Gingerbread!
+            TranslateAnimation swipeAnim = new TranslateAnimation(deltaX, deltaX, 0, 0);
+            mCurrentX = deltaX;
+            mCurrentAlpha = (1 - fraction);
+            AlphaAnimation alphaAnim = new AlphaAnimation(mCurrentAlpha, mCurrentAlpha);
+            AnimationSet set = new AnimationSet(true);
+            set.addAnimation(swipeAnim);
+            set.addAnimation(alphaAnim);
+            set.setFillAfter(true);
+            set.setFillEnabled(true);
+            view.startAnimation(set);
+        }
+    }
+
+    /**
+     * This method animates all other views in the ListView container (not including ignoreView)
+     * into their final positions. It is called after ignoreView has been removed from the
+     * adapter, but before layout has been run. The approach here is to figure out where
+     * everything is now, then allow layout to run, then figure out where everything is after
+     * layout, and then to run animations between all of those start/end positions.
+     */
+    private void animateOtherViews(final ListView listview, View viewToRemove) {
+        int firstVisiblePosition = listview.getFirstVisiblePosition();
+        for (int i = 0; i < listview.getChildCount(); ++i) {
+            View child = listview.getChildAt(i);
+            int position = firstVisiblePosition + i;
+            long itemId = mAdapter.getItemId(position);
+            if (child != viewToRemove) {
+                mItemIdTopMap.put(itemId, child.getTop());
+            }
+        }
+        // Delete the item from the adapter
+        int position = mListView.getPositionForView(viewToRemove);
+        mAdapter.remove(mAdapter.getItem(position));
+        
+        // After layout runs, capture position of all itemIDs, compare to pre-layout
+        // positions, and animate changes
+        final ViewTreeObserver observer = listview.getViewTreeObserver();
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            public boolean onPreDraw() {
+                observer.removeOnPreDrawListener(this);
+                boolean firstAnimation = true;
+                int firstVisiblePosition = listview.getFirstVisiblePosition();
+                for (int i = 0; i < listview.getChildCount(); ++i) {
+                    final View child = listview.getChildAt(i);
+                    int position = firstVisiblePosition + i;
+                    long itemId = mAdapter.getItemId(position);
+                    Integer startTop = mItemIdTopMap.get(itemId);
+                    int top = child.getTop();
+                    if (startTop == null) {
+                        // Animate new views along with the others. The catch is that they did not
+                        // exist in the start state, so we must calculate their starting position
+                        // based on whether they're coming in from the bottom (i > 0) or top.
+                        int childHeight = child.getHeight() + listview.getDividerHeight();
+                        startTop = top + (i > 0 ? childHeight : -childHeight);
+                    }
+                    int delta = startTop - top;
+                    if (delta != 0) {
+                        Runnable endAction = firstAnimation ?
+                            new Runnable() {
+                                public void run() {
+                                    mBackgroundContainer.hideBackground();
+                                    mSwiping = false;
+                                    mAnimating = false;
+                                    mListView.setEnabled(true);
+                                }
+                            } :
+                            null;
+                        firstAnimation = false;
+                        moveView(child, 0, 0, delta, 0, endAction);
+                    }
+                }
+                mItemIdTopMap.clear();
+                return true;
+            }
+        });
+    }
+    
+    /**
+     * Animate a view between start and end X/Y locations, using either old (pre-3.0) or
+     * new animation APIs.
+     */
+    @SuppressLint("NewApi")
+    private void moveView(View view, float startX, float endX, float startY, float endY,
+            Runnable endAction) {
+        final Runnable finalEndAction = endAction;
+        if (isRuntimePostGingerbread()) {
+            view.animate().setDuration(MOVE_DURATION);
+            if (startX != endX) {
+                ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX);
+                anim.setDuration(MOVE_DURATION);
+                anim.start();
+                setAnimatorEndAction(anim, endAction);
+                endAction = null;
+            }
+            if (startY != endY) {
+                ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY);
+                anim.setDuration(MOVE_DURATION);
+                anim.start();
+                setAnimatorEndAction(anim, endAction);
+            }
+        } else {
+            TranslateAnimation translator = new TranslateAnimation(startX, endX, startY, endY);
+            translator.setDuration(MOVE_DURATION);
+            view.startAnimation(translator);
+            if (endAction != null) {
+                view.getAnimation().setAnimationListener(new AnimationListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animation animation) {
+                        finalEndAction.run();
+                    }
+                });
+            }
+        }
+    }
+    
+    @SuppressLint("NewApi")
+    private void setAnimatorEndAction(Animator animator, final Runnable endAction) {
+        if (endAction != null) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            });
+        }
+    }
+
+    private void setAnimationEndAction(Animation animation, final Runnable endAction) {
+        if (endAction != null) {
+            animation.setAnimationListener(new AnimationListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    endAction.run();
+                }
+            });
+        }
+    }
+
+    /**
+     * Utility, to avoid having to implement every method in AnimationListener in
+     * every implementation class
+     */
+    static class AnimationListenerAdapter implements AnimationListener {
+
+        @Override
+        public void onAnimationEnd(Animation animation) {
+        }
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {
+        }
+
+        @Override
+        public void onAnimationStart(Animation animation) {
+        }
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/StableArrayAdapter.java b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/StableArrayAdapter.java
new file mode 100644
index 0000000..4bddc5e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewItemAnimations/src/com/example/android/listviewitemanimations/StableArrayAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.example.android.listviewitemanimations;
+
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+public class StableArrayAdapter extends ArrayAdapter<String> {
+
+    HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
+    View.OnTouchListener mTouchListener;
+
+    public StableArrayAdapter(Context context, int textViewResourceId,
+            List<String> objects, View.OnTouchListener listener) {
+        super(context, textViewResourceId, objects);
+        mTouchListener = listener;
+        for (int i = 0; i < objects.size(); ++i) {
+            mIdMap.put(objects.get(i), i);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        String item = getItem(position);
+        return mIdMap.get(item);
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view = super.getView(position, convertView, parent);
+        if (view != convertView) {
+            // Add touch listener to every new view to track swipe motion
+            view.setOnTouchListener(mTouchListener);
+        }
+        return view;
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/AndroidManifest.xml b/samples/devbytes/animation/ListViewRemovalAnimation/AndroidManifest.xml
new file mode 100644
index 0000000..f030747
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.listviewremovalanimation"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:theme="@style/WithoutBackground"
+            android:name="com.example.android.listviewremovalanimation.ListViewRemovalAnimation"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/shadowed_background.9.png b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/shadowed_background.9.png
new file mode 100644
index 0000000..f905bbc
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/shadowed_background.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/tv_background_with_divider.9.png b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/tv_background_with_divider.9.png
new file mode 100644
index 0000000..e6e3cd5
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/drawable-xhdpi/tv_background_with_divider.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/activity_list_view_deletion.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/activity_list_view_deletion.xml
new file mode 100644
index 0000000..26aa123
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/activity_list_view_deletion.xml
@@ -0,0 +1,37 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ListViewAnimations" >
+
+    <view
+        class="com.example.android.listviewremovalanimation.BackgroundContainer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/listViewBackground">
+
+        <ListView
+            android:id="@+id/listview"
+            android:divider="@null"
+            android:dividerHeight="0dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </view>
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/opaque_text_view.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/opaque_text_view.xml
new file mode 100644
index 0000000..c37f62d
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/layout/opaque_text_view.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/tv_background_with_divider"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+/>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v11/styles.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v11/styles.xml
new file mode 100644
index 0000000..139d283
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v11/styles.xml
@@ -0,0 +1,25 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v14/styles.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v14/styles.xml
new file mode 100644
index 0000000..8ac4522
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/values/strings.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/values/strings.xml
new file mode 100644
index 0000000..7125618
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!-- 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="app_name">ListViewRemovalAnimation</string>
+    <string name="use_positions">Use Positions</string>
+    <string name="delete_selected">Delete Selected</string>
+
+</resources>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/res/values/styles.xml b/samples/devbytes/animation/ListViewRemovalAnimation/res/values/styles.xml
new file mode 100644
index 0000000..c7f2e79
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/res/values/styles.xml
@@ -0,0 +1,37 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="WithoutBackground" parent="AppTheme">
+        <item name="android:windowBackground">@null</item>
+    </style>
+</resources>
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/BackgroundContainer.java b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/BackgroundContainer.java
new file mode 100644
index 0000000..1d0e806
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/BackgroundContainer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.example.android.listviewremovalanimation;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class BackgroundContainer extends FrameLayout {
+
+    boolean mShowing = false;
+    Drawable mShadowedBackground;
+    int mOpenAreaTop, mOpenAreaBottom, mOpenAreaHeight;
+    boolean mUpdateBounds = false;
+    
+    public BackgroundContainer(Context context) {
+        super(context);
+        init();
+    }
+
+    public BackgroundContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public BackgroundContainer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        mShadowedBackground =
+                getContext().getResources().getDrawable(R.drawable.shadowed_background);
+    }
+
+    public void showBackground(int top, int bottom) {
+        setWillNotDraw(false);
+        mOpenAreaTop = top;
+        mOpenAreaHeight = bottom;
+        mShowing = true;
+        mUpdateBounds = true;
+    }
+    
+    public void hideBackground() {
+        setWillNotDraw(true);
+        mShowing = false;
+    }
+    
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mShowing) {
+            	if (mUpdateBounds) {
+                mShadowedBackground.setBounds(0, 0, getWidth(), mOpenAreaHeight);
+            	}
+            canvas.save();
+            canvas.translate(0, mOpenAreaTop);
+            mShadowedBackground.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/Cheeses.java b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/Cheeses.java
new file mode 100644
index 0000000..4f4b88b
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * 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.example.android.listviewremovalanimation;
+
+public class Cheeses {
+
+    public static final String[] sCheeseStrings = {
+            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+    };
+
+}
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/ListViewRemovalAnimation.java b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/ListViewRemovalAnimation.java
new file mode 100644
index 0000000..1e3ee8c
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/ListViewRemovalAnimation.java
@@ -0,0 +1,248 @@
+/*
+ * 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.example.android.listviewremovalanimation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.widget.ListView;
+
+/**
+ * This example shows how to use a swipe effect to remove items from a ListView,
+ * and how to use animations to complete the swipe as well as to animate the other
+ * items in the list into their final places.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=NewCSg2JKLk.
+ */
+public class ListViewRemovalAnimation extends Activity {
+
+    StableArrayAdapter mAdapter;
+    ListView mListView;
+    BackgroundContainer mBackgroundContainer;
+    boolean mSwiping = false;
+    boolean mItemPressed = false;
+    HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>();
+
+    private static final int SWIPE_DURATION = 250;
+    private static final int MOVE_DURATION = 150;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_view_deletion);
+        
+        mBackgroundContainer = (BackgroundContainer) findViewById(R.id.listViewBackground);
+        mListView = (ListView) findViewById(R.id.listview);
+        android.util.Log.d("Debug", "d=" + mListView.getDivider());
+        final ArrayList<String> cheeseList = new ArrayList<String>();
+        for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
+            cheeseList.add(Cheeses.sCheeseStrings[i]);
+        }
+        mAdapter = new StableArrayAdapter(this,R.layout.opaque_text_view, cheeseList,
+                mTouchListener);
+        mListView.setAdapter(mAdapter);
+    }
+
+    /**
+     * Handle touch events to fade/move dragged items as they are swiped out
+     */
+    private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+        
+        float mDownX;
+        private int mSwipeSlop = -1;
+        
+        @Override
+        public boolean onTouch(final View v, MotionEvent event) {
+            if (mSwipeSlop < 0) {
+                mSwipeSlop = ViewConfiguration.get(ListViewRemovalAnimation.this).
+                        getScaledTouchSlop();
+            }
+            switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                if (mItemPressed) {
+                    // Multi-item swipes not handled
+                    return false;
+                }
+                mItemPressed = true;
+                mDownX = event.getX();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                v.setAlpha(1);
+                v.setTranslationX(0);
+                mItemPressed = false;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                {
+                    float x = event.getX() + v.getTranslationX();
+                    float deltaX = x - mDownX;
+                    float deltaXAbs = Math.abs(deltaX);
+                    if (!mSwiping) {
+                        if (deltaXAbs > mSwipeSlop) {
+                            mSwiping = true;
+                            mListView.requestDisallowInterceptTouchEvent(true);
+                            mBackgroundContainer.showBackground(v.getTop(), v.getHeight());
+                        }
+                    }
+                    if (mSwiping) {
+                        v.setTranslationX((x - mDownX));
+                        v.setAlpha(1 - deltaXAbs / v.getWidth());
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                {
+                    // User let go - figure out whether to animate the view out, or back into place
+                    if (mSwiping) {
+                        float x = event.getX() + v.getTranslationX();
+                        float deltaX = x - mDownX;
+                        float deltaXAbs = Math.abs(deltaX);
+                        float fractionCovered;
+                        float endX;
+                        float endAlpha;
+                        final boolean remove;
+                        if (deltaXAbs > v.getWidth() / 4) {
+                            // Greater than a quarter of the width - animate it out
+                            fractionCovered = deltaXAbs / v.getWidth();
+                            endX = deltaX < 0 ? -v.getWidth() : v.getWidth();
+                            endAlpha = 0;
+                            remove = true;
+                        } else {
+                            // Not far enough - animate it back
+                            fractionCovered = 1 - (deltaXAbs / v.getWidth());
+                            endX = 0;
+                            endAlpha = 1;
+                            remove = false;
+                        }
+                        // Animate position and alpha of swiped item
+                        // NOTE: This is a simplified version of swipe behavior, for the
+                        // purposes of this demo about animation. A real version should use
+                        // velocity (via the VelocityTracker class) to send the item off or
+                        // back at an appropriate speed.
+                        long duration = (int) ((1 - fractionCovered) * SWIPE_DURATION);
+                        mListView.setEnabled(false);
+                        v.animate().setDuration(duration).
+                                alpha(endAlpha).translationX(endX).
+                                withEndAction(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        // Restore animated values
+                                        v.setAlpha(1);
+                                        v.setTranslationX(0);
+                                        if (remove) {
+                                            animateRemoval(mListView, v);
+                                        } else {
+                                            mBackgroundContainer.hideBackground();
+                                            mSwiping = false;
+                                            mListView.setEnabled(true);
+                                        }
+                                    }
+                                });
+                    }
+                }
+                mItemPressed = false;
+                break;
+            default: 
+                return false;
+            }
+            return true;
+        }
+    };
+
+    /**
+     * This method animates all other views in the ListView container (not including ignoreView)
+     * into their final positions. It is called after ignoreView has been removed from the
+     * adapter, but before layout has been run. The approach here is to figure out where
+     * everything is now, then allow layout to run, then figure out where everything is after
+     * layout, and then to run animations between all of those start/end positions.
+     */
+    private void animateRemoval(final ListView listview, View viewToRemove) {
+        int firstVisiblePosition = listview.getFirstVisiblePosition();
+        for (int i = 0; i < listview.getChildCount(); ++i) {
+            View child = listview.getChildAt(i);
+            if (child != viewToRemove) {
+                int position = firstVisiblePosition + i;
+                long itemId = mAdapter.getItemId(position);
+                mItemIdTopMap.put(itemId, child.getTop());
+            }
+        }
+        // Delete the item from the adapter
+        int position = mListView.getPositionForView(viewToRemove);
+        mAdapter.remove(mAdapter.getItem(position));
+
+        final ViewTreeObserver observer = listview.getViewTreeObserver();
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            public boolean onPreDraw() {
+                observer.removeOnPreDrawListener(this);
+                boolean firstAnimation = true;
+                int firstVisiblePosition = listview.getFirstVisiblePosition();
+                for (int i = 0; i < listview.getChildCount(); ++i) {
+                    final View child = listview.getChildAt(i);
+                    int position = firstVisiblePosition + i;
+                    long itemId = mAdapter.getItemId(position);
+                    Integer startTop = mItemIdTopMap.get(itemId);
+                    int top = child.getTop();
+                    if (startTop != null) {
+                        if (startTop != top) {
+                            int delta = startTop - top;
+                            child.setTranslationY(delta);
+                            child.animate().setDuration(MOVE_DURATION).translationY(0);
+                            if (firstAnimation) {
+                                child.animate().withEndAction(new Runnable() {
+                                    public void run() {
+                                        mBackgroundContainer.hideBackground();
+                                        mSwiping = false;
+                                        mListView.setEnabled(true);
+                                    }
+                                });
+                                firstAnimation = false;
+                            }
+                        }
+                    } else {
+                        // Animate new views along with the others. The catch is that they did not
+                        // exist in the start state, so we must calculate their starting position
+                        // based on neighboring views.
+                        int childHeight = child.getHeight() + listview.getDividerHeight();
+                        startTop = top + (i > 0 ? childHeight : -childHeight);
+                        int delta = startTop - top;
+                        child.setTranslationY(delta);
+                        child.animate().setDuration(MOVE_DURATION).translationY(0);
+                        if (firstAnimation) {
+                            child.animate().withEndAction(new Runnable() {
+                                public void run() {
+                                    mBackgroundContainer.hideBackground();
+                                    mSwiping = false;
+                                    mListView.setEnabled(true);
+                                }
+                            });
+                            firstAnimation = false;
+                        }
+                    }
+                }
+                mItemIdTopMap.clear();
+                return true;
+            }
+        });
+    }
+
+}
diff --git a/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/StableArrayAdapter.java b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/StableArrayAdapter.java
new file mode 100644
index 0000000..948676e
--- /dev/null
+++ b/samples/devbytes/animation/ListViewRemovalAnimation/src/com/example/android/listviewremovalanimation/StableArrayAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.example.android.listviewremovalanimation;
+
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+public class StableArrayAdapter extends ArrayAdapter<String> {
+
+    HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
+    View.OnTouchListener mTouchListener;
+
+    public StableArrayAdapter(Context context, int textViewResourceId,
+            List<String> objects, View.OnTouchListener listener) {
+        super(context, textViewResourceId, objects);
+        mTouchListener = listener;
+        for (int i = 0; i < objects.size(); ++i) {
+            mIdMap.put(objects.get(i), i);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        String item = getItem(position);
+        return mIdMap.get(item);
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        View view = super.getView(position, convertView, parent);
+        if (view != convertView) {
+            // Add touch listener to every new view to track swipe motion
+            view.setOnTouchListener(mTouchListener);
+        }
+        return view;
+    }
+
+}
diff --git a/samples/devbytes/animation/LiveButton/AndroidManifest.xml b/samples/devbytes/animation/LiveButton/AndroidManifest.xml
new file mode 100644
index 0000000..280c2f8
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.livebutton"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.livebutton.LiveButton"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/LiveButton/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/LiveButton/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/LiveButton/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/LiveButton/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/LiveButton/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/LiveButton/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/LiveButton/res/layout/activity_overshoot.xml b/samples/devbytes/animation/LiveButton/res/layout/activity_overshoot.xml
new file mode 100644
index 0000000..6900650
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/layout/activity_overshoot.xml
@@ -0,0 +1,29 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".LiveButton" >
+
+    <Button
+        android:id="@+id/clickMe"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:text="Click me!" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/LiveButton/res/values-v14/styles.xml b/samples/devbytes/animation/LiveButton/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/LiveButton/res/values/strings.xml b/samples/devbytes/animation/LiveButton/res/values/strings.xml
new file mode 100644
index 0000000..5c5ff17
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/values/strings.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="app_name">LiveButton</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="menu_settings">Settings</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/LiveButton/res/values/styles.xml b/samples/devbytes/animation/LiveButton/res/values/styles.xml
new file mode 100644
index 0000000..27658b7
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/LiveButton/src/com/example/android/livebutton/LiveButton.java b/samples/devbytes/animation/LiveButton/src/com/example/android/livebutton/LiveButton.java
new file mode 100644
index 0000000..d9f8d5e
--- /dev/null
+++ b/samples/devbytes/animation/LiveButton/src/com/example/android/livebutton/LiveButton.java
@@ -0,0 +1,65 @@
+/*
+ * 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.example.android.livebutton;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Button;
+
+/**
+ * This app shows a simple application of anticipation and follow-through techniques as
+ * the button animates into its pressed state and animates back out of it, overshooting
+ * end state before resolving.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class LiveButton extends Activity {
+    
+    DecelerateInterpolator sDecelerator = new DecelerateInterpolator();
+    OvershootInterpolator sOvershooter = new OvershootInterpolator(10f);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_overshoot);
+        
+        final Button clickMeButton = (Button) findViewById(R.id.clickMe);
+        clickMeButton.animate().setDuration(200);
+        
+        clickMeButton.setOnTouchListener(new View.OnTouchListener() {
+            
+            @Override
+            public boolean onTouch(View arg0, MotionEvent arg1) {
+                if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
+                    clickMeButton.animate().setInterpolator(sDecelerator).
+                            scaleX(.7f).scaleY(.7f);
+                } else if (arg1.getAction() == MotionEvent.ACTION_UP) {
+                    clickMeButton.animate().setInterpolator(sOvershooter).
+                            scaleX(1f).scaleY(1f);
+                }
+                return false;
+            }
+        });
+        
+    }
+}
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/AndroidManifest.xml b/samples/devbytes/animation/MultiPropertyAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..6a7a593
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.multipropertyanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.multipropertyanimations.MultiPropertyAnimations"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/layout/activity_multi_property_animations.xml b/samples/devbytes/animation/MultiPropertyAnimations/res/layout/activity_multi_property_animations.xml
new file mode 100644
index 0000000..f4826b6
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/layout/activity_multi_property_animations.xml
@@ -0,0 +1,46 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MultiPropertyAnimations" >
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="runValueAnimator"
+        android:text="Animate Me!" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="runViewPropertyAnimator"
+        android:text="Animate Me!" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="runObjectAnimators"
+        android:text="Animate Me!" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:onClick="runObjectAnimator"
+        android:text="Animate Me!" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/MultiPropertyAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/values/dimens.xml b/samples/devbytes/animation/MultiPropertyAnimations/res/values/dimens.xml
new file mode 100644
index 0000000..90db76b
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!-- 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>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/values/strings.xml b/samples/devbytes/animation/MultiPropertyAnimations/res/values/strings.xml
new file mode 100644
index 0000000..5487857
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/values/strings.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="app_name">MultiPropertyAnimations</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Hello world!</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/res/values/styles.xml b/samples/devbytes/animation/MultiPropertyAnimations/res/values/styles.xml
new file mode 100644
index 0000000..27658b7
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/MultiPropertyAnimations/src/com/example/android/multipropertyanimations/MultiPropertyAnimations.java b/samples/devbytes/animation/MultiPropertyAnimations/src/com/example/android/multipropertyanimations/MultiPropertyAnimations.java
new file mode 100644
index 0000000..e1c8054
--- /dev/null
+++ b/samples/devbytes/animation/MultiPropertyAnimations/src/com/example/android/multipropertyanimations/MultiPropertyAnimations.java
@@ -0,0 +1,92 @@
+/*
+ * 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.example.android.multipropertyanimations;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This example shows various ways of animating multiple properties in parallel.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class MultiPropertyAnimations extends Activity {
+
+    private static final float TX_START = 0;
+    private static final float TY_START = 0;
+    private static final float TX_END = 400;
+    private static final float TY_END = 200;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_multi_property_animations);
+    }
+
+    /**
+     * A very manual approach to animation uses a ValueAnimator to animate a fractional
+     * value and then turns that value into the final property values which are then set
+     * directly on the target object.
+     */
+    public void runValueAnimator(final View view) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 400);
+        anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animator) {
+                float fraction = animator.getAnimatedFraction();
+                view.setTranslationX(TX_START + fraction * (TX_END - TX_START));
+                view.setTranslationY(TY_START + fraction * (TY_END - TY_START));
+            }
+        });
+        anim.start();
+    }
+
+    /**
+     * ViewPropertyAnimator is the cleanest and most efficient way of animating
+     * View properties, even when there are multiple properties to be animated
+     * in parallel.
+     */
+    public void runViewPropertyAnimator(View view) {
+        view.animate().translationX(TX_END).translationY(TY_END);
+    }
+
+    /**
+     * Multiple ObjectAnimator objects can be created and run in parallel.
+     */
+    public void runObjectAnimators(View view) {
+        ObjectAnimator.ofFloat(view, View.TRANSLATION_X, TX_END).start();
+        ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, TY_END).start();
+        // Optional: use an AnimatorSet to run these in parallel
+    }
+    
+    /**
+     * Using PropertyValuesHolder objects enables the use of a single ObjectAnimator
+     * per target, even when there are multiple properties being animated on that target.
+     */
+    public void runObjectAnimator(View view) {
+        PropertyValuesHolder pvhTX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, TX_END);
+        PropertyValuesHolder pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, TY_END);
+        ObjectAnimator.ofPropertyValuesHolder(view, pvhTX, pvhTY).start();
+    }
+}
diff --git a/samples/devbytes/animation/PictureViewer/AndroidManifest.xml b/samples/devbytes/animation/PictureViewer/AndroidManifest.xml
new file mode 100644
index 0000000..6cb909e
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.pictureviewer"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.pictureviewer.PictureViewer"
+            android:label="@string/title_activity_picture_viewer" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/PictureViewer/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/PictureViewer/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p1.jpg b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p1.jpg
new file mode 100644
index 0000000..9745818
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p1.jpg
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p2.jpg b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p2.jpg
new file mode 100644
index 0000000..db8731f
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p2.jpg
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p3.jpg b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p3.jpg
new file mode 100644
index 0000000..b240b3a
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p3.jpg
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p4.jpg b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p4.jpg
new file mode 100644
index 0000000..4de9292
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-nodpi/p4.jpg
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/PictureViewer/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PictureViewer/res/layout/activity_picture_viewer.xml b/samples/devbytes/animation/PictureViewer/res/layout/activity_picture_viewer.xml
new file mode 100644
index 0000000..599c770
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/layout/activity_picture_viewer.xml
@@ -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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="#000"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <ImageView
+        android:id="@+id/prevImageView"
+        android:adjustViewBounds="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+
+    <ImageView
+        android:id="@+id/nextImageView"
+        android:adjustViewBounds="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+
+</FrameLayout>
diff --git a/samples/devbytes/animation/PictureViewer/res/values-v11/styles.xml b/samples/devbytes/animation/PictureViewer/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/PictureViewer/res/values-v14/styles.xml b/samples/devbytes/animation/PictureViewer/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/PictureViewer/res/values/strings.xml b/samples/devbytes/animation/PictureViewer/res/values/strings.xml
new file mode 100644
index 0000000..5ed396b
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!-- 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="app_name">PictureViewer</string>
+    <string name="title_activity_picture_viewer">PictureViewer</string>
+
+</resources>
diff --git a/samples/devbytes/animation/PictureViewer/res/values/styles.xml b/samples/devbytes/animation/PictureViewer/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/PictureViewer/src/com/example/android/pictureviewer/PictureViewer.java b/samples/devbytes/animation/PictureViewer/src/com/example/android/pictureviewer/PictureViewer.java
new file mode 100644
index 0000000..e1cb4dd
--- /dev/null
+++ b/samples/devbytes/animation/PictureViewer/src/com/example/android/pictureviewer/PictureViewer.java
@@ -0,0 +1,97 @@
+/*
+ * 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.example.android.pictureviewer;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * This example shows how to use ViewPropertyAnimator to get a cross-fade effect as new
+ * bitmaps get installed in an ImageView.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=9XbKMUtVnJA.
+ */
+public class PictureViewer extends Activity {
+
+    int mCurrentDrawable = 0;
+    int drawableIDs[] = {
+            R.drawable.p1,
+            R.drawable.p2,
+            R.drawable.p3,
+            R.drawable.p4,
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_picture_viewer);
+
+        // This app works by having two views, which get faded in/out for the cross-fade effect
+        final ImageView prevImageView = (ImageView) findViewById(R.id.prevImageView);
+        final ImageView nextImageView = (ImageView) findViewById(R.id.nextImageView);
+        prevImageView.setBackgroundColor(Color.TRANSPARENT);
+        nextImageView.setBackgroundColor(Color.TRANSPARENT);
+
+        // Setup default ViewPropertyAnimator durations for the two ImageViews
+        prevImageView.animate().setDuration(1000);
+        nextImageView.animate().setDuration(1000);
+
+        // NOte that a real app would do this more robustly, and not just load all possible
+        // bitmaps at onCreate() time.
+        final BitmapDrawable drawables[] = new BitmapDrawable[drawableIDs.length];
+        for (int i = 0; i < drawableIDs.length; ++i) {
+            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
+                    drawableIDs[i]);
+            drawables[i] = new BitmapDrawable(getResources(), bitmap);
+        }
+        prevImageView.setImageDrawable(drawables[0]);
+        nextImageView.setImageDrawable(drawables[1]);
+
+        prevImageView.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                // Use ViewPropertyAnimator to fade the previous imageView out and the next one in
+                prevImageView.animate().alpha(0).withLayer();
+                nextImageView.animate().alpha(1).withLayer().
+                        withEndAction(new Runnable() {
+                    // When the animation ends, set up references to change the prev/next
+                    // associations
+                    @Override
+                    public void run() {
+                        mCurrentDrawable =
+                                (mCurrentDrawable + 1) % drawables.length;
+                        int nextDrawableIndex =
+                                (mCurrentDrawable + 1) % drawables.length;
+                        prevImageView.setImageDrawable(drawables[mCurrentDrawable]);
+                        nextImageView.setImageDrawable(drawables[nextDrawableIndex]);
+                        nextImageView.setAlpha(0f);
+                        prevImageView.setAlpha(1f);
+                    }
+                });
+            }
+        });
+    }
+
+}
diff --git a/samples/devbytes/animation/PropertyAnimations/AndroidManifest.xml b/samples/devbytes/animation/PropertyAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..977d29e
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.propertyanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.propertyanimations.PropertyAnimations"
+            android:label="@string/title_activity_property_animations" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/animator/combo.xml b/samples/devbytes/animation/PropertyAnimations/res/animator/combo.xml
new file mode 100644
index 0000000..6ae8ca0
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/animator/combo.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:propertyName="alpha"
+        android:repeatCount="1"
+        android:repeatMode="reverse"
+        android:duration="300"
+        android:valueTo="0"/>
+    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:propertyName="translationX"
+        android:repeatCount="1"
+        android:repeatMode="reverse"
+        android:duration="300"
+        android:valueTo="800"/>
+
+    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:propertyName="rotation"
+        android:duration="300"
+        android:valueFrom="0"
+        android:valueTo="360"/>
+
+    <set xmlns:android="http://schemas.android.com/apk/res/android">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:repeatCount="1"
+            android:repeatMode="reverse"
+            android:duration="300"
+            android:valueTo="2"/>
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:repeatCount="1"
+            android:repeatMode="reverse"
+            android:duration="300"
+            android:valueTo="2"/>
+    </set>
+</set>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/animator/fade.xml b/samples/devbytes/animation/PropertyAnimations/res/animator/fade.xml
new file mode 100644
index 0000000..c27c6af
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/animator/fade.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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="alpha"
+    android:repeatCount="1"
+    android:repeatMode="reverse"
+    android:duration="300"
+    android:valueTo="0"/>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/animator/move.xml b/samples/devbytes/animation/PropertyAnimations/res/animator/move.xml
new file mode 100644
index 0000000..aaac09a
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/animator/move.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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="translationX"
+    android:repeatCount="1"
+    android:repeatMode="reverse"
+    android:duration="300"
+    android:valueTo="800"/>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/animator/scale.xml b/samples/devbytes/animation/PropertyAnimations/res/animator/scale.xml
new file mode 100644
index 0000000..7bbe0a9
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/animator/scale.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator
+        android:propertyName="scaleX"
+        android:repeatCount="1"
+        android:repeatMode="reverse"
+        android:duration="300"
+        android:valueTo="2"/>
+    <objectAnimator
+        android:propertyName="scaleY"
+        android:repeatCount="1"
+        android:repeatMode="reverse"
+        android:duration="300"
+        android:valueTo="2"/>
+</set>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/animator/spin.xml b/samples/devbytes/animation/PropertyAnimations/res/animator/spin.xml
new file mode 100644
index 0000000..64fdc36
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/animator/spin.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="rotation"
+    android:valueFrom="0"
+    android:duration="300"
+    android:valueTo="360"/>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/PropertyAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PropertyAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/PropertyAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PropertyAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/PropertyAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/PropertyAnimations/res/layout/activity_property_animations.xml b/samples/devbytes/animation/PropertyAnimations/res/layout/activity_property_animations.xml
new file mode 100644
index 0000000..add0a54
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/layout/activity_property_animations.xml
@@ -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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <CheckBox
+        android:id="@+id/checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/use_animation_resources" />
+
+    <Button
+        android:id="@+id/alphaButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/alpha" />
+
+    <Button
+        android:id="@+id/translateButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/translate" />
+
+    <Button
+        android:id="@+id/rotateButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rotate" />
+
+    <Button
+        android:id="@+id/scaleButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/scale" />
+
+    <Button
+        android:id="@+id/setButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/values-v11/styles.xml b/samples/devbytes/animation/PropertyAnimations/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/PropertyAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/values/strings.xml b/samples/devbytes/animation/PropertyAnimations/res/values/strings.xml
new file mode 100644
index 0000000..3a872d8
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!-- 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="app_name">PropertyAnimations</string>
+    <string name="title_activity_property_animations">PropertyAnimations</string>
+    <string name="use_animation_resources">Use Animation Resources</string>
+    <string name="alpha">Alpha</string>
+    <string name="translate">Translate</string>
+    <string name="rotate">Rotate</string>
+    <string name="scale">Scale</string>
+    <string name="set">Set</string>
+
+</resources>
diff --git a/samples/devbytes/animation/PropertyAnimations/res/values/styles.xml b/samples/devbytes/animation/PropertyAnimations/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/PropertyAnimations/src/com/example/android/propertyanimations/PropertyAnimations.java b/samples/devbytes/animation/PropertyAnimations/src/com/example/android/propertyanimations/PropertyAnimations.java
new file mode 100644
index 0000000..89480f6
--- /dev/null
+++ b/samples/devbytes/animation/PropertyAnimations/src/com/example/android/propertyanimations/PropertyAnimations.java
@@ -0,0 +1,111 @@
+/*
+ * 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.example.android.propertyanimations;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+
+/**
+ * This example shows how to use property animations, specifically ObjectAnimator, to perform
+ * various view animations. Compare this approach to that of the ViewAnimations demo, which
+ * shows how to achieve similar effects using the pre-3.0 animation APIs.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=3UbJhmkeSig.
+ */
+public class PropertyAnimations extends Activity {
+
+    CheckBox mCheckBox;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_property_animations);
+
+        mCheckBox = (CheckBox) findViewById(R.id.checkbox);
+        final Button alphaButton = (Button) findViewById(R.id.alphaButton);
+        final Button translateButton = (Button) findViewById(R.id.translateButton);
+        final Button rotateButton = (Button) findViewById(R.id.rotateButton);
+        final Button scaleButton = (Button) findViewById(R.id.scaleButton);
+        final Button setButton = (Button) findViewById(R.id.setButton);
+
+        // Fade the button out and back in
+        ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(alphaButton,
+                View.ALPHA, 0);
+        alphaAnimation.setRepeatCount(1);
+        alphaAnimation.setRepeatMode(ValueAnimator.REVERSE);
+
+        // Move the button over to the right and then back
+        ObjectAnimator translateAnimation =
+                ObjectAnimator.ofFloat(translateButton, View.TRANSLATION_X, 800);
+        translateAnimation.setRepeatCount(1);
+        translateAnimation.setRepeatMode(ValueAnimator.REVERSE);
+
+        // Spin the button around in a full circle
+        ObjectAnimator rotateAnimation =
+                ObjectAnimator.ofFloat(rotateButton, View.ROTATION, 360);
+        rotateAnimation.setRepeatCount(1);
+        rotateAnimation.setRepeatMode(ValueAnimator.REVERSE);
+
+        // Scale the button in X and Y. Note the use of PropertyValuesHolder to animate
+        // multiple properties on the same object in parallel.
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 2);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 2);
+        ObjectAnimator scaleAnimation =
+                ObjectAnimator.ofPropertyValuesHolder(scaleButton, pvhX, pvhY);
+        scaleAnimation.setRepeatCount(1);
+        scaleAnimation.setRepeatMode(ValueAnimator.REVERSE);
+
+        // Run the animations above in sequence
+        AnimatorSet setAnimation = new AnimatorSet();
+        setAnimation.play(translateAnimation).after(alphaAnimation).before(rotateAnimation);
+        setAnimation.play(rotateAnimation).before(scaleAnimation);
+
+        setupAnimation(alphaButton, alphaAnimation, R.animator.fade);
+        setupAnimation(translateButton, translateAnimation, R.animator.move);
+        setupAnimation(rotateButton, rotateAnimation, R.animator.spin);
+        setupAnimation(scaleButton, scaleAnimation, R.animator.scale);
+        setupAnimation(setButton, setAnimation, R.animator.combo);
+
+    }
+
+    private void setupAnimation(View view, final Animator animation, final int animationID) {
+        view.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                // If the button is checked, load the animation from the given resource
+                // id instead of using the passed-in animation parameter. See the xml files
+                // for the details on those animations.
+                if (mCheckBox.isChecked()) {
+                    Animator anim = AnimatorInflater.loadAnimator(PropertyAnimations.this, animationID);
+                    anim.setTarget(v);
+                    anim.start();
+                    return;
+                }
+                animation.start();
+            }
+        });
+    }
+}
diff --git a/samples/devbytes/animation/SquashAndStretch/AndroidManifest.xml b/samples/devbytes/animation/SquashAndStretch/AndroidManifest.xml
new file mode 100644
index 0000000..82e9b1b
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.squashandstretch"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.squashandstretch.SquashAndStretch"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/SquashAndStretch/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/SquashAndStretch/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/SquashAndStretch/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/SquashAndStretch/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/SquashAndStretch/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/SquashAndStretch/res/layout/main.xml b/samples/devbytes/animation/SquashAndStretch/res/layout/main.xml
new file mode 100644
index 0000000..ea6793d
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/layout/main.xml
@@ -0,0 +1,31 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/container"
+    tools:context=".SquashAndStretch" >
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:onClick="onButtonClick"
+        android:text="Click Me!" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/res/menu/main.xml b/samples/devbytes/animation/SquashAndStretch/res/menu/main.xml
new file mode 100644
index 0000000..aab540e
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/menu/main.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/menu_slow"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/menu_slow_animations"
+        android:checkable="true"/>
+
+</menu>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/res/values-v14/styles.xml b/samples/devbytes/animation/SquashAndStretch/res/values-v14/styles.xml
new file mode 100644
index 0000000..6e9521a
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/res/values/strings.xml b/samples/devbytes/animation/SquashAndStretch/res/values/strings.xml
new file mode 100644
index 0000000..3fb1b96
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/values/strings.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="app_name">SquashAndStretch</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="menu_slow_animations">Slow</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/res/values/styles.xml b/samples/devbytes/animation/SquashAndStretch/res/values/styles.xml
new file mode 100644
index 0000000..27658b7
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/SquashAndStretch/src/com/example/squashandstretch/SquashAndStretch.java b/samples/devbytes/animation/SquashAndStretch/src/com/example/squashandstretch/SquashAndStretch.java
new file mode 100644
index 0000000..328dd50
--- /dev/null
+++ b/samples/devbytes/animation/SquashAndStretch/src/com/example/squashandstretch/SquashAndStretch.java
@@ -0,0 +1,114 @@
+/*
+ * 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.example.squashandstretch;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This example shows how to add some life to a view during animation by deforming the shape.
+ * As the button "falls", it stretches along the line of travel. When it hits the bottom, it
+ * squashes, like a real object when hitting a surface. Then the button reverses these actions
+ * to bounce back up to the start.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class SquashAndStretch extends Activity {
+
+    private static final AccelerateInterpolator sAccelerator = new AccelerateInterpolator();
+    private static final DecelerateInterpolator sDecelerator = new DecelerateInterpolator();
+
+    ViewGroup mContainer = null;
+    private static final long BASE_DURATION = 300;
+    private long sAnimatorScale = 1;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+        
+        mContainer = (ViewGroup) findViewById(R.id.container);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.main, menu);
+        return true;
+    }
+    
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.menu_slow) {
+            sAnimatorScale = item.isChecked() ? 1 : 5;
+            item.setChecked(!item.isChecked());
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void onButtonClick(View view) {
+        long animationDuration = (long) (BASE_DURATION * sAnimatorScale);
+
+        // Scale around bottom/middle to simplify squash against the window bottom
+        view.setPivotX(view.getWidth() / 2);
+        view.setPivotY(view.getHeight());
+        
+        // Animate the button down, accelerating, while also stretching in Y and squashing in X
+        PropertyValuesHolder pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
+                mContainer.getHeight() - view.getHeight());
+        PropertyValuesHolder pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, .7f);
+        PropertyValuesHolder pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.2f);
+        ObjectAnimator downAnim = ObjectAnimator.ofPropertyValuesHolder(
+                view, pvhTY, pvhSX, pvhSY);
+        downAnim.setInterpolator(sAccelerator);
+        downAnim.setDuration((long) (animationDuration * 2));
+
+        // Stretch in X, squash in Y, then reverse
+        pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, 2);
+        pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, .5f);
+        ObjectAnimator stretchAnim =
+                ObjectAnimator.ofPropertyValuesHolder(view, pvhSX, pvhSY);
+        stretchAnim.setRepeatCount(1);
+        stretchAnim.setRepeatMode(ValueAnimator.REVERSE);
+        stretchAnim.setInterpolator(sDecelerator);
+        stretchAnim.setDuration(animationDuration);
+        
+        // Animate back to the start
+        pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0);
+        pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
+        pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
+        ObjectAnimator upAnim =
+                ObjectAnimator.ofPropertyValuesHolder(view, pvhTY, pvhSX, pvhSY);
+        upAnim.setDuration((long) (animationDuration * 2));
+        upAnim.setInterpolator(sDecelerator);
+
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(downAnim, stretchAnim, upAnim);
+        set.start();
+    }
+}
diff --git a/samples/devbytes/animation/ToonGame/AndroidManifest.xml b/samples/devbytes/animation/ToonGame/AndroidManifest.xml
new file mode 100644
index 0000000..505b7ce
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.toongame"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.toongame.ToonGame"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="com.example.android.toongame.PlayerSetupActivity"
+            android:windowSoftInputMode="adjustResize"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        </application>
+
+</manifest>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ToonGame/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/blue_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/blue_oval.xml
new file mode 100644
index 0000000..2ffe16a
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/blue_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#00f"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/cyan_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/cyan_oval.xml
new file mode 100644
index 0000000..53ea6ac
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/cyan_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#0ff"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_button.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_button.xml
new file mode 100644
index 0000000..3ed5b67
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_button.xml
@@ -0,0 +1,19 @@
+<?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:drawable="@drawable/green_down" android:state_pressed="true"/>
+    <item android:drawable="@drawable/green_up"/>
+</selector>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_down.9.png b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_down.9.png
new file mode 100644
index 0000000..db974ba
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_down.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_oval.xml
new file mode 100644
index 0000000..03b83c3
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#0f0"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_up.9.png b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_up.9.png
new file mode 100644
index 0000000..0c2f5c8
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/green_up.9.png
Binary files differ
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/magenta_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/magenta_oval.xml
new file mode 100644
index 0000000..675b0dc
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/magenta_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#f0f"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/red_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/red_oval.xml
new file mode 100644
index 0000000..f2276a6
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/red_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#f00"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-mdpi/yellow_oval.xml b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/yellow_oval.xml
new file mode 100644
index 0000000..d03f684
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-mdpi/yellow_oval.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    
+    <solid android:color="#ff0"/>
+
+</shape>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ToonGame/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ToonGame/res/layout/activity_toon_game.xml b/samples/devbytes/animation/ToonGame/res/layout/activity_toon_game.xml
new file mode 100644
index 0000000..d65301b
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/layout/activity_toon_game.xml
@@ -0,0 +1,47 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/container"
+    tools:context=".ToonGame" >
+
+    <Button
+        android:id="@+id/startButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="64dip"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:textColor="#00c"
+        android:text="Play!"
+        android:background="@drawable/green_button"
+        android:visibility="invisible"
+        android:onClick="play"
+        android:textStyle="bold" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/startButton"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="39dp"
+        android:text="@string/welcome"
+        android:textColor="@android:color/holo_blue_bright"
+        android:textSize="64dip"
+        android:textStyle="bold" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/layout/player_setup_layout.xml b/samples/devbytes/animation/ToonGame/res/layout/player_setup_layout.xml
new file mode 100644
index 0000000..c55d39b
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/layout/player_setup_layout.xml
@@ -0,0 +1,187 @@
+<!-- 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.
+-->
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <RelativeLayout 
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false">
+    
+        <view
+            android:id="@+id/nameTV"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="120dp"
+            class="com.example.android.toongame.SkewableTextView"
+            android:text="Name?"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textSize="@dimen/bigText" />
+    
+        <view
+            class="com.example.android.toongame.SkewableTextView"
+            android:id="@+id/ageTV"
+            android:visibility="gone"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="120dp"
+            android:text="Difficulty?"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textSize="@dimen/bigText" />
+    
+        <view
+            class="com.example.android.toongame.SkewableTextView"
+            android:id="@+id/creditTV"
+            android:visibility="gone"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:layout_marginTop="120dp"
+            android:gravity="center"
+            android:text="Parent's Credit Card Number?"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textSize="64sp" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:visibility="gone"
+            android:id="@+id/nameButtons"
+            android:layout_marginBottom="132dp" >
+            
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/green_oval"
+                android:text="Bob"
+                android:textSize="36sp"
+                android:textColor="#fff"
+                android:textStyle="bold"
+                android:onClick="selectName"
+                android:id="@+id/bobButton"/>
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/blue_oval"
+                android:text="Jane"
+                android:textSize="36sp"
+                android:textColor="#fff"
+                android:textStyle="bold"
+                android:onClick="selectName"
+                android:id="@+id/janeButton"/>
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/magenta_oval"
+                android:text="Pat"
+                android:textSize="36sp"
+                android:textColor="#fff"
+                android:textStyle="bold"
+                 android:onClick="selectName"
+                android:id="@+id/patButton"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:visibility="gone"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:id="@+id/difficultyButtons"
+            android:layout_marginBottom="50dp" >
+            
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/green_oval"
+                android:text="Easy"
+                android:textSize="36sp"
+                android:textColor="#fff"
+                android:textStyle="bold"
+                android:onClick="selectDifficulty"
+                android:id="@+id/easyButton"/>
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/blue_oval"
+                android:text="Hard"
+                android:textSize="36sp"
+                android:textColor="#fff"
+                android:textStyle="bold"
+                android:onClick="selectDifficulty"
+                android:id="@+id/hardButton"/>
+
+            <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="10dip"
+                android:padding="15dip"
+                android:background="@drawable/red_oval"
+                android:text="Mega Hard"
+                android:textSize="36sp"
+                android:textColor="#000"
+                android:textStyle="bold|italic"
+                android:onClick="selectDifficulty"
+                android:id="@+id/megaHardButton"/>
+
+        </LinearLayout>
+        
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:visibility="gone"
+            android:id="@+id/creditButtons1"
+            android:layout_marginBottom="132dp" />
+        
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:visibility="gone"
+            android:id="@+id/creditButtons2"
+            android:layout_marginBottom="70dp" />
+        
+        
+    </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/values/dimens.xml b/samples/devbytes/animation/ToonGame/res/values/dimens.xml
new file mode 100644
index 0000000..fc229f6
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<!-- 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>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="bigText">64dip</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/values/strings.xml b/samples/devbytes/animation/ToonGame/res/values/strings.xml
new file mode 100644
index 0000000..fb256e6
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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="app_name">ToonGame</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="welcome">Welcome!</string>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/res/values/styles.xml b/samples/devbytes/animation/ToonGame/res/values/styles.xml
new file mode 100644
index 0000000..9d91d6a
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/PlayerSetupActivity.java b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/PlayerSetupActivity.java
new file mode 100644
index 0000000..fce11c3
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/PlayerSetupActivity.java
@@ -0,0 +1,360 @@
+/*
+ * 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.example.android.toongame;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+
+/**
+ * This activity, launched from the ToonGame activity, takes the user between three
+ * different setup screens where they choose a name, choose a difficulty rating, and
+ * enter important financial information. All of the screens are meant to be
+ * simple, engaging, and fun.
+ */
+public class PlayerSetupActivity extends Activity {
+
+    private static final AccelerateInterpolator sAccelerator = new AccelerateInterpolator();
+    private static final LinearInterpolator sLinearInterpolator = new LinearInterpolator();
+    ViewGroup mContainer;
+    EditText mEditText;
+    
+    private static final int NAME_STATE = 0;
+    private static final int DIFFICULTY_STATE = 1;
+    private static final int CREDIT_STATE = 2;
+    private int mEntryState = NAME_STATE;
+
+    SkewableTextView mNameTV, mDifficultyTV, mCreditTV;
+    
+    ViewGroup mNameButtons, mDifficultyButtons, mCreditButtons1, mCreditButtons2;
+    
+    Button mBobButton, mJaneButton, mPatButton;
+    
+    private static final TimeInterpolator sOvershooter = new OvershootInterpolator();
+    private static final DecelerateInterpolator sDecelerator = new DecelerateInterpolator();
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.player_setup_layout);
+        overridePendingTransition(0, 0);
+        
+        mContainer = (ViewGroup) findViewById(R.id.container);
+        mContainer.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+        
+        mNameTV = (SkewableTextView) findViewById(R.id.nameTV);
+        mDifficultyTV = (SkewableTextView) findViewById(R.id.ageTV);
+        mCreditTV = (SkewableTextView) findViewById(R.id.creditTV);
+        
+        mBobButton = setupButton(R.id.bobButton);
+        setupButton(R.id.janeButton);
+        setupButton(R.id.patButton);
+        setupButton(R.id.easyButton);
+        setupButton(R.id.hardButton);
+        setupButton(R.id.megaHardButton);
+        
+        mNameButtons = (ViewGroup) findViewById(R.id.nameButtons);
+        mDifficultyButtons = (ViewGroup) findViewById(R.id.difficultyButtons);
+        mCreditButtons1 = (ViewGroup) findViewById(R.id.creditButtons1);
+        mCreditButtons2 = (ViewGroup) findViewById(R.id.creditButtons2);
+    }
+    
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
+
+    private Button setupButton(int resourceId) {
+        Button button = (Button) findViewById(resourceId);
+        button.setOnTouchListener(mButtonPressListener);
+        return button;
+    }
+    
+    private View.OnTouchListener mButtonPressListener =
+            new View.OnTouchListener() {
+                public boolean onTouch(View v, MotionEvent event) {
+                    switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        v.animate().setDuration(ToonGame.SHORT_DURATION).
+                                scaleX(.8f).scaleY(.8f).setInterpolator(sDecelerator);
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        v.animate().setDuration(ToonGame.SHORT_DURATION).
+                                scaleX(1).scaleY(1).setInterpolator(sAccelerator);
+                        break;
+                    default:
+                        break;
+                    }
+                    return false;
+                }
+            };
+
+    public void buttonClick(View clickedView, int alignmentRule) {
+        ViewGroup parent = (ViewGroup) clickedView.getParent();
+        for (int i = 0; i < parent.getChildCount(); ++i) {
+            Button child = (Button) parent.getChildAt(i);
+            if (child != clickedView) {
+                child.animate().alpha(0);
+            } else {
+                final Button buttonCopy = new Button(this);
+                child.setVisibility(View.INVISIBLE);
+                buttonCopy.setBackground(child.getBackground());
+                buttonCopy.setText(((Button) child).getText());
+                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+                        RelativeLayout.LayoutParams.WRAP_CONTENT,
+                        RelativeLayout.LayoutParams.WRAP_CONTENT);
+                params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+                params.addRule(alignmentRule);
+                params.setMargins(25, 50, 25, 50);
+                buttonCopy.setLayoutParams(params);
+                buttonCopy.setPadding(child.getPaddingLeft(), child.getPaddingTop(),
+                        child.getPaddingRight(), child.getPaddingBottom());
+                buttonCopy.setTextSize(TypedValue.COMPLEX_UNIT_PX, child.getTextSize());
+                buttonCopy.setTypeface(child.getTypeface(), Typeface.BOLD);
+                ColorStateList colors = child.getTextColors();
+                buttonCopy.setTextColor(colors.getDefaultColor());
+                final int[] oldLocationInWindow = new int[2];
+                child.getLocationInWindow(oldLocationInWindow);
+                mContainer.addView(buttonCopy);
+                buttonCopy.getViewTreeObserver().addOnPreDrawListener(
+                        new ViewTreeObserver.OnPreDrawListener() {
+                    
+                    @Override
+                    public boolean onPreDraw() {
+                        buttonCopy.getViewTreeObserver().removeOnPreDrawListener(this);
+                        int[] locationInWindow = new int[2];
+                        buttonCopy.getLocationInWindow(locationInWindow);
+                        float deltaX = oldLocationInWindow[0] - locationInWindow[0];
+                        float deltaY = oldLocationInWindow[1] - locationInWindow[1];
+    
+                        buttonCopy.setTranslationX(deltaX);
+                        buttonCopy.setTranslationY(deltaY);
+                        
+                        PropertyValuesHolder pvhSX =
+                                PropertyValuesHolder.ofFloat(View.SCALE_X, 3);
+                        PropertyValuesHolder pvhSY =
+                                PropertyValuesHolder.ofFloat(View.SCALE_Y, 3);
+                        ObjectAnimator bounceAnim = ObjectAnimator.ofPropertyValuesHolder(
+                                buttonCopy, pvhSX, pvhSY);
+                        bounceAnim.setRepeatCount(1);
+                        bounceAnim.setRepeatMode(ValueAnimator.REVERSE);
+                        bounceAnim.setInterpolator(sDecelerator);
+                        bounceAnim.setDuration(300);
+                        
+                        PropertyValuesHolder pvhTX =
+                                PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0);
+                        PropertyValuesHolder pvhTY =
+                                PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0);
+                        ObjectAnimator moveAnim = ObjectAnimator.ofPropertyValuesHolder(
+                                buttonCopy, pvhTX, pvhTY);
+                        moveAnim.setDuration(600);
+                        bounceAnim.start();
+                        moveAnim.start();
+                        moveAnim.addListener(new AnimatorListenerAdapter() {
+                            public void onAnimationEnd(Animator animation) {
+                                switch (mEntryState) {
+                                case (NAME_STATE) :
+                                {
+                                    Runnable runnable = new Runnable() {
+                                        public void run() {
+                                            mDifficultyButtons.setVisibility(View.VISIBLE);
+                                            mNameButtons.setVisibility(View.GONE);
+                                            popChildrenIn(mDifficultyButtons, null);
+                                        }
+                                    };
+                                    slideToNext(mNameTV, mDifficultyTV, runnable);
+                                    mEntryState = DIFFICULTY_STATE;
+                                    break;
+                                }
+                                case (DIFFICULTY_STATE) :
+                                {
+                                    mDifficultyButtons.setVisibility(View.GONE);
+                                    for (int i = 0; i < 5; ++i) {
+                                        mCreditButtons1.addView(setupNumberButton(i));
+                                    }
+                                    for (int i = 5; i < 10; ++i) {
+                                        mCreditButtons2.addView(setupNumberButton(i));
+                                    }
+                                    Runnable runnable = new Runnable() {
+                                        public void run() {
+                                            mCreditButtons1.setVisibility(View.VISIBLE);
+                                            Runnable runnable = new Runnable() {
+                                                public void run() {
+                                                    mCreditButtons2.setVisibility(View.VISIBLE);
+                                                    popChildrenIn(mCreditButtons2, null);
+                                                }
+                                            };
+                                            popChildrenIn(mCreditButtons1, runnable);
+                                        }
+                                    };
+                                    slideToNext(mDifficultyTV, mCreditTV, runnable);
+                                    mEntryState = CREDIT_STATE;
+                                }
+                                    break;
+                                }
+                            }
+                        });
+                        return true;
+                    }
+                });
+            }
+        }
+    }
+    
+    public void selectDifficulty(View clickedView) {
+        buttonClick(clickedView, RelativeLayout.ALIGN_PARENT_RIGHT);
+    }
+    
+    public void selectName(View clickedView) {
+        buttonClick(clickedView, RelativeLayout.ALIGN_PARENT_LEFT);
+    }
+    
+    private Button setupNumberButton(int number) {
+        Button button = new Button(PlayerSetupActivity.this);
+        button.setTextSize(15);
+        button.setTextColor(Color.WHITE);
+        button.setTypeface(mBobButton.getTypeface(), Typeface.BOLD);
+        button.setText(Integer.toString(number));
+        button.setPadding(0, 0, 0, 0);
+        
+        OvalShape oval = new OvalShape();
+        ShapeDrawable drawable = new ShapeDrawable(oval);
+        drawable.getPaint().setColor(0xFF << 24 | (int) (50 + 150 * Math.random()) << 16 |
+                (int) (50 + 150 * Math.random()) << 8 |  (int) (50 + 150 * Math.random()));
+        button.setBackground(drawable);
+
+        button.setOnTouchListener(mButtonPressListener);
+
+        return button;
+    }
+
+    ViewTreeObserver.OnPreDrawListener mPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+        
+        @Override
+        public boolean onPreDraw() {
+            mContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+            mContainer.setScaleX(0);
+            mContainer.setScaleY(0);
+            mContainer.animate().scaleX(1).scaleY(1).setInterpolator(new OvershootInterpolator());
+            mContainer.animate().setDuration(ToonGame.LONG_DURATION).withEndAction(new Runnable() {
+                
+                @Override
+                public void run() {
+                    ViewGroup buttonsParent = (ViewGroup) findViewById(R.id.nameButtons);
+                    buttonsParent.setVisibility(View.VISIBLE);
+                    popChildrenIn(buttonsParent, null);
+                }
+            });
+            return false;
+        }
+    };
+    
+    private void popChildrenIn(ViewGroup parent, final Runnable endAction) {
+        // for all children, scale in one at a time
+        TimeInterpolator overshooter = new OvershootInterpolator();
+        int childCount = parent.getChildCount();
+        ObjectAnimator[] childAnims = new ObjectAnimator[childCount];
+        for (int i = 0; i < childCount; ++i) {
+            View child = parent.getChildAt(i);
+            child.setScaleX(0);
+            child.setScaleY(0);
+            PropertyValuesHolder pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
+            PropertyValuesHolder pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
+            ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(child, pvhSX, pvhSY);
+            anim.setDuration(150);
+            anim.setInterpolator(overshooter);
+            childAnims[i] = anim;
+        }
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(childAnims);
+        set.start();
+        if (endAction != null) {
+            set.addListener(new AnimatorListenerAdapter() {
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            });
+        }
+    }
+    
+    private void slideToNext(final SkewableTextView currentView,
+            final SkewableTextView nextView, final Runnable endAction) {
+        // skew/anticipate current view, slide off, set GONE, restore translation
+        ObjectAnimator currentSkewer = ObjectAnimator.ofFloat(currentView, "skewX", -.5f);
+        currentSkewer.setInterpolator(sDecelerator);
+        ObjectAnimator currentMover = ObjectAnimator.ofFloat(currentView, View.TRANSLATION_X,
+                -mContainer.getWidth());
+        currentMover.setInterpolator(sLinearInterpolator);
+        currentMover.setDuration(ToonGame.MEDIUM_DURATION);
+        
+        // set next view visible, translate off to right, skew,
+        // slide on in parallel, overshoot/wobble, unskew
+        nextView.setVisibility(View.VISIBLE);
+        nextView.setSkewX(-.5f);
+        nextView.setTranslationX(mContainer.getWidth());
+        
+        ObjectAnimator nextMover = ObjectAnimator.ofFloat(nextView, View.TRANSLATION_X, 0);
+        nextMover.setInterpolator(sAccelerator);
+        nextMover.setDuration(ToonGame.MEDIUM_DURATION);
+        ObjectAnimator nextSkewer = ObjectAnimator.ofFloat(nextView, "skewX", 0);
+        nextSkewer.setInterpolator(sOvershooter);
+        
+        AnimatorSet moverSet = new AnimatorSet();
+        moverSet.playTogether(currentMover, nextMover);
+        AnimatorSet fullSet = new AnimatorSet();
+        fullSet.playSequentially(currentSkewer, moverSet, nextSkewer);
+        fullSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                currentView.setSkewX(0);
+                currentView.setVisibility(View.GONE);
+                currentView.setTranslationX(0);
+                if (endAction != null) {
+                    endAction.run();
+                }
+            }
+        });
+        
+        fullSet.start();
+    }
+
+}
diff --git a/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/SkewableTextView.java b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/SkewableTextView.java
new file mode 100644
index 0000000..9ea15ca
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/SkewableTextView.java
@@ -0,0 +1,84 @@
+/*
+ * 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.example.android.toongame;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * This custom TextView can be skewed to the left or right to enable anticipation and
+ * follow-through effects
+ */
+public class SkewableTextView extends TextView {
+
+    private float mSkewX;
+    RectF mTempRect = new RectF();
+    
+    public SkewableTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public SkewableTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SkewableTextView(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mSkewX != 0) {
+            canvas.translate(0, getHeight());
+            canvas.skew(mSkewX, 0);
+            canvas.translate(0,  -getHeight());
+        }
+        super.onDraw(canvas);
+    }
+
+    public float getSkewX() {
+        return mSkewX;
+    }
+    
+    public void setSkewX(float value) {
+        if (value != mSkewX) {
+            mSkewX = value;
+            invalidate();             // force redraw with new skew value
+            invalidateSkewedBounds(); // also invalidate appropriate area of parent
+        }
+    }
+    
+    /**
+     * Need to invalidate proper area of parent for skewed bounds
+     */
+    private void invalidateSkewedBounds() {
+        if (mSkewX != 0) {
+            Matrix matrix = new Matrix();
+            matrix.setSkew(-mSkewX, 0);
+            mTempRect.set(0, 0, getRight(), getBottom());
+            matrix.mapRect(mTempRect);
+            mTempRect.offset(getLeft() + getTranslationX(), getTop() + getTranslationY());
+            ((View) getParent()).invalidate((int) mTempRect.left, (int) mTempRect.top,
+                    (int) (mTempRect.right +.5f), (int) (mTempRect.bottom + .5f));
+        }
+    }
+}
diff --git a/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/ToonGame.java b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/ToonGame.java
new file mode 100644
index 0000000..b03eeeb
--- /dev/null
+++ b/samples/devbytes/animation/ToonGame/src/com/example/android/toongame/ToonGame.java
@@ -0,0 +1,219 @@
+/*
+ * 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.example.android.toongame;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.Button;
+
+/**
+ * This application shows various cartoon animation techniques in the context of
+ * a larger application, to show how such animations might be used to create a more
+ * interactive, fun, and engaging experience.
+ *
+ * This main activity launches a sub-activity when the Play button is clicked. The
+ * main action in this master activity is bouncing the Play button in, randomly
+ * bouncing it while waiting for input, and animating its press and click behaviors
+ * when the user interacts with it. 
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on the DevBytes playlist in the androiddevelopers channel on YouTube at
+ * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0.
+ */
+public class ToonGame extends Activity {
+
+    Button mStarter;
+    ViewGroup mContainer;
+    private static final AccelerateInterpolator sAccelerator = new AccelerateInterpolator();
+    private static final DecelerateInterpolator sDecelerator = new DecelerateInterpolator();
+    private static final LinearInterpolator sLinearInterpolator = new LinearInterpolator();
+    static long SHORT_DURATION = 100;
+    static long MEDIUM_DURATION = 200;
+    static long REGULAR_DURATION = 300;
+    static long LONG_DURATION = 500;
+    
+    private static float sDurationScale = 1f;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
+        setContentView(R.layout.activity_toon_game);
+        
+        mStarter = (Button) findViewById(R.id.startButton);
+        mContainer = (ViewGroup) findViewById(R.id.container);
+        mStarter.setOnTouchListener(funButtonListener);
+        mStarter.animate().setDuration(100);
+        
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mContainer.setScaleX(1);
+        mContainer.setScaleY(1);
+        mContainer.setAlpha(1);
+        mStarter.setVisibility(View.INVISIBLE);
+        mContainer.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mStarter.removeCallbacks(mSquishRunnable);
+    }
+
+    private OnTouchListener funButtonListener = new OnTouchListener() {
+        
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mStarter.animate().scaleX(.8f).scaleY(.8f).setInterpolator(sDecelerator);
+                mStarter.setTextColor(Color.CYAN);
+                mStarter.removeCallbacks(mSquishRunnable);
+                mStarter.setPressed(true);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float x = event.getX();
+                float y = event.getY();
+                boolean isInside = (x > 0 && x < mStarter.getWidth() &&
+                        y > 0 && y < mStarter.getHeight());
+                if (mStarter.isPressed() != isInside) {
+                    mStarter.setPressed(isInside);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mStarter.isPressed()) {
+                    mStarter.performClick();
+                    mStarter.setPressed(false);
+                } else {
+                    mStarter.animate().scaleX(1).scaleY(1).setInterpolator(sAccelerator);
+                }
+                mStarter.setTextColor(Color.BLUE);
+                break;
+            }
+            return true;
+        }
+    };
+
+    private Runnable mSquishRunnable = new Runnable() {
+        public void run() {
+            squishyBounce(mStarter, 0,
+                    mContainer.getHeight() - mStarter.getTop() - mStarter.getHeight(),
+                    0, .5f, 1.5f);
+        }
+    };
+    
+    public void play(View view) {
+        mContainer.animate().scaleX(5).scaleY(5).alpha(0).setDuration(LONG_DURATION).
+                setInterpolator(sLinearInterpolator).
+                withEndAction(new Runnable() {
+            @Override
+            public void run() {
+                mStarter.postOnAnimation(new Runnable() {
+                    public void run() {
+                        Intent intent = new Intent(ToonGame.this,
+                                PlayerSetupActivity.class);
+                        startActivity(intent);
+                        overridePendingTransition(0, 0);
+                    }
+                });
+            }
+        });
+        view.removeCallbacks(mSquishRunnable);
+    }
+    
+    private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                
+                @Override
+                public boolean onPreDraw() {
+                    mContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+                    mContainer.postDelayed(new Runnable() {
+                        public void run() {
+                            // Drop in the button from off the top of the screen
+                            mStarter.setVisibility(View.VISIBLE);
+                            mStarter.setY(-mStarter.getHeight());
+                            squishyBounce(mStarter, 
+                                    -(mStarter.getTop() + mStarter.getHeight()),
+                                    mContainer.getHeight() - mStarter.getTop() -
+                                            mStarter.getHeight(),
+                                    0, .5f, 1.5f);
+                        }
+                    }, 500);
+                    return true;
+                }
+            };
+
+    private void squishyBounce(final View view, final float startTY, final float bottomTY,
+            final float endTY, final float squash, final float stretch) {
+        view.setPivotX(view.getWidth() / 2);
+        view.setPivotY(view.getHeight());
+        PropertyValuesHolder pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
+                startTY, bottomTY);
+        PropertyValuesHolder pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, .7f);
+        PropertyValuesHolder pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.2f);
+        ObjectAnimator downAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhTY, pvhSX, pvhSY);
+        downAnim.setInterpolator(sAccelerator);
+
+        pvhTY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, bottomTY, endTY);
+        pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
+        pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
+        ObjectAnimator upAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhTY, pvhSX, pvhSY);
+        upAnim.setInterpolator(sDecelerator);
+        
+        pvhSX = PropertyValuesHolder.ofFloat(View.SCALE_X, stretch);
+        pvhSY = PropertyValuesHolder.ofFloat(View.SCALE_Y, squash);
+        ObjectAnimator stretchAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhSX, pvhSY);
+        stretchAnim.setRepeatCount(1);
+        stretchAnim.setRepeatMode(ValueAnimator.REVERSE);
+        stretchAnim.setInterpolator(sDecelerator);
+        
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(downAnim, stretchAnim, upAnim);
+        set.setDuration(getDuration(SHORT_DURATION));
+        set.start();
+        set.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                view.postDelayed(mSquishRunnable, (long) (500 + Math.random() * 2000));
+            }
+        });
+    }
+    
+    public static long getDuration(long baseDuration) {
+        return (long) (baseDuration * sDurationScale);
+    }
+    
+
+}
diff --git a/samples/devbytes/animation/ViewAnimations/AndroidManifest.xml b/samples/devbytes/animation/ViewAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..d713f4c
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.viewanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.viewanimations.ViewAnimations"
+            android:label="@string/title_activity_view_animations" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/ViewAnimations/res/anim/alpha_anim.xml b/samples/devbytes/animation/ViewAnimations/res/anim/alpha_anim.xml
new file mode 100644
index 0000000..2afcb6c
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/anim/alpha_anim.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:duration="500" />
diff --git a/samples/devbytes/animation/ViewAnimations/res/anim/rotate_anim.xml b/samples/devbytes/animation/ViewAnimations/res/anim/rotate_anim.xml
new file mode 100644
index 0000000..622bd32
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/anim/rotate_anim.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:toDegrees="360"
+        android:pivotX="50%" android:pivotY="50%"
+        android:duration="1000"/>
diff --git a/samples/devbytes/animation/ViewAnimations/res/anim/scale_anim.xml b/samples/devbytes/animation/ViewAnimations/res/anim/scale_anim.xml
new file mode 100644
index 0000000..1b10705
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/anim/scale_anim.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.
+-->
+<scale xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXScale="1"
+        android:fromYScale="1"
+        android:toXScale="2"
+        android:toYScale="2"
+        android:duration="1000"/>
diff --git a/samples/devbytes/animation/ViewAnimations/res/anim/set_anim.xml b/samples/devbytes/animation/ViewAnimations/res/anim/set_anim.xml
new file mode 100644
index 0000000..567e3d5
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/anim/set_anim.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:duration="1000" />
+    <rotate android:toDegrees="180"
+           android:duration="1000"/>
+    <scale android:toXScale="2"
+           android:toYScale="2"
+           android:duration="1000"/>
+    <translate android:fromXDelta="0" android:toXDelta="100%p"
+           android:fromYDelta="0" android:toYDelta="100"
+           android:duration="1000" />
+</set>
diff --git a/samples/devbytes/animation/ViewAnimations/res/anim/translate_anim.xml b/samples/devbytes/animation/ViewAnimations/res/anim/translate_anim.xml
new file mode 100644
index 0000000..e33e8f1
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/anim/translate_anim.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXDelta="0" android:toXDelta="100%p"
+        android:fromYDelta="0" android:toYDelta="100"
+        android:duration="1000" />
diff --git a/samples/devbytes/animation/ViewAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ViewAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ViewAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ViewAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ViewAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ViewAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/ViewAnimations/res/layout/activity_view_animations.xml b/samples/devbytes/animation/ViewAnimations/res/layout/activity_view_animations.xml
new file mode 100644
index 0000000..3338135
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/layout/activity_view_animations.xml
@@ -0,0 +1,57 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <CheckBox
+        android:id="@+id/checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/use_animation_resources" />
+
+    <Button
+        android:id="@+id/alphaButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/alpha" />
+
+    <Button
+        android:id="@+id/translateButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/translate" />
+
+    <Button
+        android:id="@+id/rotateButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/rotate" />
+
+    <Button
+        android:id="@+id/scaleButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/scale" />
+
+    <Button
+        android:id="@+id/setButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/ViewAnimations/res/values-v11/styles.xml b/samples/devbytes/animation/ViewAnimations/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/ViewAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/ViewAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/ViewAnimations/res/values/strings.xml b/samples/devbytes/animation/ViewAnimations/res/values/strings.xml
new file mode 100644
index 0000000..a43f1af
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!-- 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="app_name">ViewAnimations</string>
+    <string name="title_activity_view_animations">ViewAnimations</string>
+    <string name="alpha">Alpha</string>
+    <string name="translate">Translate</string>
+    <string name="rotate">Rotate</string>
+    <string name="scale">Scale</string>
+    <string name="set">Set</string>
+    <string name="use_animation_resources">Use Animation Resources</string>
+
+</resources>
diff --git a/samples/devbytes/animation/ViewAnimations/res/values/styles.xml b/samples/devbytes/animation/ViewAnimations/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/ViewAnimations/src/com/example/android/viewanimations/ViewAnimations.java b/samples/devbytes/animation/ViewAnimations/src/com/example/android/viewanimations/ViewAnimations.java
new file mode 100644
index 0000000..50d98a6
--- /dev/null
+++ b/samples/devbytes/animation/ViewAnimations/src/com/example/android/viewanimations/ViewAnimations.java
@@ -0,0 +1,104 @@
+/*
+ * 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.example.android.viewanimations;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.RotateAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.Button;
+import android.widget.CheckBox;
+
+/**
+ * This example shows how to use pre-3.0 view Animation classes to create various animated UI
+ * effects. See also the demo PropertyAnimations, which shows how this is done using the new
+ * ObjectAnimator API introduced in Android 3.0.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=_UWXqFBF86U.
+ */
+public class ViewAnimations extends Activity {
+
+    CheckBox mCheckBox;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_view_animations);
+
+        mCheckBox = (CheckBox) findViewById(R.id.checkbox);
+        final Button alphaButton = (Button) findViewById(R.id.alphaButton);
+        final Button translateButton = (Button) findViewById(R.id.translateButton);
+        final Button rotateButton = (Button) findViewById(R.id.rotateButton);
+        final Button scaleButton = (Button) findViewById(R.id.scaleButton);
+        final Button setButton = (Button) findViewById(R.id.setButton);
+
+        // Fade the button out and back in
+        final AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
+        alphaAnimation.setDuration(1000);
+
+        // Move the button over and then back
+        final TranslateAnimation translateAnimation =
+                new TranslateAnimation(Animation.ABSOLUTE, 0,
+                Animation.RELATIVE_TO_PARENT, 1,
+                Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 100);
+        translateAnimation.setDuration(1000);
+
+        // Spin the button around in a full circle
+        final RotateAnimation rotateAnimation = new RotateAnimation(0, 360,
+                Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f);
+        rotateAnimation.setDuration(1000);
+
+        // Scale the button in X and Y.
+        final ScaleAnimation scaleAnimation = new ScaleAnimation(1, 2, 1, 2);
+        scaleAnimation.setDuration(1000);
+
+        // Run the animations above in sequence on the final button. Looks horrible.
+        final AnimationSet setAnimation = new AnimationSet(true);
+        setAnimation.addAnimation(alphaAnimation);
+        setAnimation.addAnimation(translateAnimation);
+        setAnimation.addAnimation(rotateAnimation);
+        setAnimation.addAnimation(scaleAnimation);
+
+        setupAnimation(alphaButton, alphaAnimation, R.anim.alpha_anim);
+        setupAnimation(translateButton, translateAnimation, R.anim.translate_anim);
+        setupAnimation(rotateButton, rotateAnimation, R.anim.rotate_anim);
+        setupAnimation(scaleButton, scaleAnimation, R.anim.scale_anim);
+        setupAnimation(setButton, setAnimation, R.anim.set_anim);
+
+    }
+
+    private void setupAnimation(View view, final Animation animation,
+            final int animationID) {
+        view.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                // If the button is checked, load the animation from the given resource
+                // id instead of using the passed-in animation paramter. See the xml files
+                // for the details on those animations.
+                v.startAnimation(mCheckBox.isChecked() ?
+                        AnimationUtils.loadAnimation(ViewAnimations.this, animationID) :
+                        animation);
+            }
+        });
+    }
+}
diff --git a/samples/devbytes/animation/WindowAnimations/AndroidManifest.xml b/samples/devbytes/animation/WindowAnimations/AndroidManifest.xml
new file mode 100644
index 0000000..6f111eb
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.windowanimations"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".WindowAnimations"
+            android:label="@string/title_activity_window_animations" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".AnimatedSubActivity"
+            android:label="@string/title_activity_window_anim_sub" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="com.example.android.windowanimations.SubActivity"
+            android:label="@string/title_activity_sub" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_left.xml b/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_left.xml
new file mode 100644
index 0000000..8321f1f
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_left.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXDelta="100%p" android:toXDelta="0"
+        android:duration="500" />
diff --git a/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_right.xml b/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_right.xml
new file mode 100644
index 0000000..c3f2920
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/anim/slide_in_right.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXDelta="-50%p" android:toXDelta="0"
+        android:duration="500" />
diff --git a/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_left.xml b/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_left.xml
new file mode 100644
index 0000000..6c93ced
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_left.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXDelta="0" android:toXDelta="-50%p"
+        android:duration="500" />
diff --git a/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_right.xml b/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_right.xml
new file mode 100644
index 0000000..2057345
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/anim/slide_out_right.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromXDelta="0" android:toXDelta="100%p"
+        android:duration="500" />
diff --git a/samples/devbytes/animation/WindowAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/WindowAnimations/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/WindowAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/WindowAnimations/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/WindowAnimations/res/drawable-nodpi/thumbnail.png b/samples/devbytes/animation/WindowAnimations/res/drawable-nodpi/thumbnail.png
new file mode 100644
index 0000000..e1722f3
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/drawable-nodpi/thumbnail.png
Binary files differ
diff --git a/samples/devbytes/animation/WindowAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/WindowAnimations/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/animation/WindowAnimations/res/layout/activity_sub.xml b/samples/devbytes/animation/WindowAnimations/res/layout/activity_sub.xml
new file mode 100644
index 0000000..3864752
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/layout/activity_sub.xml
@@ -0,0 +1,30 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="#888"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:text="@string/activity_text"
+        android:textSize="40dip"
+        tools:context=".SubActivity" />
+
+</RelativeLayout>
diff --git a/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_anim_sub.xml b/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_anim_sub.xml
new file mode 100644
index 0000000..709e539
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_anim_sub.xml
@@ -0,0 +1,30 @@
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="#888"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:text="@string/activity_text"
+        android:textSize="40dip"
+        tools:context=".WindowAnimSubActivity" />
+
+</RelativeLayout>
diff --git a/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_animations.xml b/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_animations.xml
new file mode 100644
index 0000000..8d653ea
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/layout/activity_window_animations.xml
@@ -0,0 +1,44 @@
+<!-- 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <Button android:id="@+id/defaultButton"
+        android:text="Default Animations"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:context=".WindowAnimations" />
+
+    <Button android:id="@+id/translateButton"
+        android:text="Translate Animations"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:context=".WindowAnimations" />
+
+    <Button android:id="@+id/scaleButton"
+        android:text="Scale Animations"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:context=".WindowAnimations" />
+
+    <ImageView android:id="@+id/thumbnail"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/thumbnail" />
+
+</LinearLayout>
diff --git a/samples/devbytes/animation/WindowAnimations/res/values-v11/styles.xml b/samples/devbytes/animation/WindowAnimations/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/WindowAnimations/res/values-v14/styles.xml b/samples/devbytes/animation/WindowAnimations/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/animation/WindowAnimations/res/values/strings.xml b/samples/devbytes/animation/WindowAnimations/res/values/strings.xml
new file mode 100644
index 0000000..39fc436
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/values/strings.xml
@@ -0,0 +1,25 @@
+<!-- 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="app_name">WindowAnimations</string>
+    <string name="activity_text">Sub-Activity</string>
+    <string name="menu_settings">Settings</string>
+    <string name="title_activity_window_animations">WindowAnimations</string>
+    <string name="title_activity_window_anim_sub">WindowAnimSubActivity</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="title_activity_sub">SubActivity</string>
+
+</resources>
diff --git a/samples/devbytes/animation/WindowAnimations/res/values/styles.xml b/samples/devbytes/animation/WindowAnimations/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/AnimatedSubActivity.java b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/AnimatedSubActivity.java
new file mode 100644
index 0000000..ad26248
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/AnimatedSubActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.example.android.windowanimations;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * See WindowAnimations.java for comments on the overall application.
+ *
+ * This is a sub-activity which provides custom animation behavior. When this activity
+ * is exited, the user will see the behavior specified in the overridePendingTransition() call.
+ */
+public class AnimatedSubActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_window_anim_sub);
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_right);
+    }
+}
diff --git a/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/SubActivity.java b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/SubActivity.java
new file mode 100644
index 0000000..61b0a68
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/SubActivity.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.example.android.windowanimations;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * See WindowAnimations.java for comments on the overall application.
+ *
+ * This is a sub-activity which does not provide any custom animation. Exiting this
+ * sub-activity therefore gets the default system behavior for window animations.
+ */
+public class SubActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_sub);
+    }
+}
diff --git a/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/WindowAnimations.java b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/WindowAnimations.java
new file mode 100644
index 0000000..a5c76b1
--- /dev/null
+++ b/samples/devbytes/animation/WindowAnimations/src/com/example/android/windowanimations/WindowAnimations.java
@@ -0,0 +1,106 @@
+/*
+ * 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.example.android.windowanimations;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+/**
+ * This example shows how to create custom Window animations to animate between different
+ * sub-activities.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=Ho8vk61lVIU.
+ */
+public class WindowAnimations extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_window_animations);
+
+        final Button defaultButton = (Button) findViewById(R.id.defaultButton);
+        final Button translateButton = (Button) findViewById(R.id.translateButton);
+        final Button scaleButton = (Button) findViewById(R.id.scaleButton);
+        final ImageView thumbnail = (ImageView) findViewById(R.id.thumbnail);
+
+        // By default, launching a sub-activity uses the system default for window animations
+        defaultButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent subActivity = new Intent(WindowAnimations.this,
+                        SubActivity.class);
+                startActivity(subActivity);
+            }
+        });
+
+        // Custom animations allow us to do things like slide the next activity in as we
+        // slide this activity out
+        translateButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // Using the AnimatedSubActivity also allows us to animate exiting that
+                // activity - see that activity for details
+                Intent subActivity = new Intent(WindowAnimations.this,
+                        AnimatedSubActivity.class);
+                // The enter/exit animations for the two activities are specified by xml resources
+                Bundle translateBundle =
+                        ActivityOptions.makeCustomAnimation(WindowAnimations.this,
+                        R.anim.slide_in_left, R.anim.slide_out_left).toBundle();
+                startActivity(subActivity, translateBundle);
+            }
+        });
+
+        // Starting in Jellybean, you can provide an animation that scales up the new
+        // activity from a given source rectangle
+        scaleButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent subActivity = new Intent(WindowAnimations.this,
+                        AnimatedSubActivity.class);
+                Bundle scaleBundle = ActivityOptions.makeScaleUpAnimation(
+                        v, 0, 0, v.getWidth(), v.getHeight()).toBundle();
+                startActivity(subActivity, scaleBundle);
+            }
+        });
+
+        // Starting in Jellybean, you can also provide an animation that scales up the new
+        // activity from a given bitmap, cross-fading between the starting and ending
+        // representations. Here, we scale up from a thumbnail image of the final sub-activity
+        thumbnail.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                BitmapDrawable drawable = (BitmapDrawable) thumbnail.getDrawable();
+                Bitmap bm = drawable.getBitmap();
+                Intent subActivity = new Intent(WindowAnimations.this, AnimatedSubActivity.class);
+                Bundle scaleBundle = ActivityOptions.makeThumbnailScaleUpAnimation(
+                        thumbnail, bm, 0, 0).toBundle();
+                startActivity(subActivity, scaleBundle);
+            }
+        });
+
+
+    }
+
+}
diff --git a/samples/devbytes/graphics/BitmapAllocation/AndroidManifest.xml b/samples/devbytes/graphics/BitmapAllocation/AndroidManifest.xml
new file mode 100644
index 0000000..5ffcb29
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.bitmapallocation"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="15"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.bitmapallocation.BitmapAllocation"
+            android:label="@string/title_activity_bitmap_allocation" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapAllocation/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapAllocation/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/a.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/a.jpg
new file mode 100644
index 0000000..23dc04a
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/a.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/b.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/b.jpg
new file mode 100644
index 0000000..b6beca2
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/b.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/c.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/c.jpg
new file mode 100644
index 0000000..d608554
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/c.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/d.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/d.jpg
new file mode 100644
index 0000000..f1ff9f5
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/d.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/e.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/e.jpg
new file mode 100644
index 0000000..580a0d1
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/e.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/f.jpg b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/f.jpg
new file mode 100644
index 0000000..bc14d12
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-nodpi/f.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapAllocation/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/layout/activity_bitmap_allocation.xml b/samples/devbytes/graphics/BitmapAllocation/res/layout/activity_bitmap_allocation.xml
new file mode 100644
index 0000000..458178b
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/layout/activity_bitmap_allocation.xml
@@ -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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <CheckBox
+            android:id="@+id/checkbox"
+        android:text="@string/reuse_bitmap"
+        android:checked="false"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <TextView
+            android:id="@+id/loadDuration"
+            android:textSize="30sp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <ImageView
+        android:id="@+id/imageview"
+        android:adjustViewBounds="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/values-v11/styles.xml b/samples/devbytes/graphics/BitmapAllocation/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/values-v14/styles.xml b/samples/devbytes/graphics/BitmapAllocation/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/values/strings.xml b/samples/devbytes/graphics/BitmapAllocation/res/values/strings.xml
new file mode 100644
index 0000000..bf598f9
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!-- 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="app_name">BitmapAllocation</string>
+    <string name="title_activity_bitmap_allocation">BitmapAllocation</string>
+    <string name="reuse_bitmap">Reuse Bitmap</string>
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapAllocation/res/values/styles.xml b/samples/devbytes/graphics/BitmapAllocation/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapAllocation/src/com/example/android/bitmapallocation/BitmapAllocation.java b/samples/devbytes/graphics/BitmapAllocation/src/com/example/android/bitmapallocation/BitmapAllocation.java
new file mode 100644
index 0000000..cc6b1e0
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapAllocation/src/com/example/android/bitmapallocation/BitmapAllocation.java
@@ -0,0 +1,99 @@
+/*
+ * 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.example.android.bitmapallocation;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This example shows how to speed up bitmap loading and reduce garbage collection
+ * by reusing existing bitmaps.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com,
+ * or on YouTube at https://www.youtube.com/watch?v=rsQet4nBVi8.
+ */
+public class BitmapAllocation extends Activity {
+
+    // There are some assumptions in this demo app that don't carry over well to the real world:
+    // it assumes that all bitmaps are the same size and that loading all bitmaps as the activity
+    // starts is good enough. A real application would be take a more flexible and robust
+    // approach. But these assumptions are good enough for the purposes of this tutorial,
+    // which is about reusing existing bitmaps of the same size.
+
+    int mCurrentIndex = 0;
+    Bitmap mCurrentBitmap = null;
+    BitmapFactory.Options mBitmapOptions;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bitmap_allocation);
+
+        final int[] imageIDs = {R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d,
+                R.drawable.e, R.drawable.f};
+
+        final CheckBox checkbox = (CheckBox) findViewById(R.id.checkbox);
+        final TextView durationTextview = (TextView) findViewById(R.id.loadDuration);
+        final ImageView imageview = (ImageView) findViewById(R.id.imageview);
+
+        // Create bitmap to be re-used, based on the size of one of the bitmaps
+        mBitmapOptions = new BitmapFactory.Options();
+        mBitmapOptions.inJustDecodeBounds = true;
+        BitmapFactory.decodeResource(getResources(), R.drawable.a, mBitmapOptions);
+        mCurrentBitmap = Bitmap.createBitmap(mBitmapOptions.outWidth,
+                mBitmapOptions.outHeight, Bitmap.Config.ARGB_8888);
+        mBitmapOptions.inJustDecodeBounds = false;
+        mBitmapOptions.inBitmap = mCurrentBitmap;
+        mBitmapOptions.inSampleSize = 1;
+        BitmapFactory.decodeResource(getResources(), R.drawable.a, mBitmapOptions);
+        imageview.setImageBitmap(mCurrentBitmap);
+
+        // When the user clicks on the image, load the next one in the list
+        imageview.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                mCurrentIndex = (mCurrentIndex + 1) % imageIDs.length;
+                BitmapFactory.Options bitmapOptions = null;
+                if (checkbox.isChecked()) {
+                    // Re-use the bitmap by using BitmapOptions.inBitmap
+                    bitmapOptions = mBitmapOptions;
+                    bitmapOptions.inBitmap = mCurrentBitmap;
+                }
+                long startTime = System.currentTimeMillis();
+                mCurrentBitmap = BitmapFactory.decodeResource(getResources(),
+                        imageIDs[mCurrentIndex], bitmapOptions);
+                imageview.setImageBitmap(mCurrentBitmap);
+
+                // One way you can see the difference between reusing and not is through the
+                // timing reported here. But you can also see a huge impact in the garbage
+                // collector if you look at logcat with and without reuse. Avoiding garbage
+                // collection when possible, especially for large items like bitmaps,
+                // is always a good idea.
+                durationTextview.setText("Load took " +
+                        (System.currentTimeMillis() - startTime));
+            }
+        });
+    }
+
+}
diff --git a/samples/devbytes/graphics/BitmapScaling/AndroidManifest.xml b/samples/devbytes/graphics/BitmapScaling/AndroidManifest.xml
new file mode 100644
index 0000000..f926a27
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.bitmapscaling"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="15" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.bitmapscaling.BitmapScaling"
+            android:label="@string/title_activity_bitmap_scaling" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/graphics/BitmapScaling/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapScaling/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapScaling/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapScaling/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapScaling/res/drawable-nodpi/jellybean_statue.jpg b/samples/devbytes/graphics/BitmapScaling/res/drawable-nodpi/jellybean_statue.jpg
new file mode 100644
index 0000000..9f5349a
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/drawable-nodpi/jellybean_statue.jpg
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapScaling/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/graphics/BitmapScaling/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/graphics/BitmapScaling/res/layout/activity_bitmap_scaling.xml b/samples/devbytes/graphics/BitmapScaling/res/layout/activity_bitmap_scaling.xml
new file mode 100644
index 0000000..d9556fc
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/layout/activity_bitmap_scaling.xml
@@ -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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:text="@string/original_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <ImageView
+        android:id="@+id/originalImageHolder"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:text="@string/scaled_images"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <LinearLayout
+        android:id="@+id/scaledImageContainer"
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/samples/devbytes/graphics/BitmapScaling/res/values-v11/styles.xml b/samples/devbytes/graphics/BitmapScaling/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapScaling/res/values-v14/styles.xml b/samples/devbytes/graphics/BitmapScaling/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapScaling/res/values/strings.xml b/samples/devbytes/graphics/BitmapScaling/res/values/strings.xml
new file mode 100644
index 0000000..466f6ae
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/values/strings.xml
@@ -0,0 +1,22 @@
+<!-- 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="app_name">BitmapScaling</string>
+    <string name="title_activity_bitmap_scaling">BitmapScaling</string>
+    <string name="original_image">Original Image</string>
+    <string name="scaled_images">Scaled Images</string>
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapScaling/res/values/styles.xml b/samples/devbytes/graphics/BitmapScaling/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/graphics/BitmapScaling/src/com/example/android/bitmapscaling/BitmapScaling.java b/samples/devbytes/graphics/BitmapScaling/src/com/example/android/bitmapscaling/BitmapScaling.java
new file mode 100644
index 0000000..8ca5a31
--- /dev/null
+++ b/samples/devbytes/graphics/BitmapScaling/src/com/example/android/bitmapscaling/BitmapScaling.java
@@ -0,0 +1,75 @@
+/*
+ * 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.example.android.bitmapscaling;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+
+/**
+ * This example shows how the use of BitmapOptions affects the resulting size of a loaded
+ * bitmap. Sub-sampling can speed up load times and reduce the need for large bitmaps
+ * in memory if your target bitmap size is much smaller, although it's good to understand
+ * that you can't get specific Bitmap sizes, but rather power-of-two reductions in sizes.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=12cB7gnL6po.
+ */
+public class BitmapScaling extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_bitmap_scaling);
+
+        LinearLayout container = (LinearLayout) findViewById(R.id.scaledImageContainer);
+        ImageView originalImageView = (ImageView) findViewById(R.id.originalImageHolder);
+
+        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.jellybean_statue);
+        originalImageView.setImageBitmap(bitmap);
+
+        for (int i = 2; i < 10; ++i) {
+            addScaledImageView(bitmap, i, container);
+        }
+    }
+
+    private void addScaledImageView(Bitmap original, int sampleSize, LinearLayout container) {
+
+        // inSampleSize tells the loader how much to scale the final image, which it does at
+        // load time by simply reading less pixels for every pixel value in the final bitmap.
+        // Note that it only scales by powers of two, so a value of two results in a bitmap
+        // 1/2 the size of the original and a value of four results in a bitmap 1/4 the original
+        // size. Intermediate values are rounded down, so a value of three results in a bitmap 1/2
+        // the original size.
+
+        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
+        bitmapOptions.inSampleSize = sampleSize;
+
+        Bitmap scaledBitmap = BitmapFactory.decodeResource(getResources(),
+                R.drawable.jellybean_statue, bitmapOptions);
+        ImageView scaledImageView = new ImageView(this);
+        scaledImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT));
+        scaledImageView.setImageBitmap(scaledBitmap);
+        container.addView(scaledImageView);
+    }
+}
diff --git a/samples/devbytes/ui/ListViewDeletion/AndroidManifest.xml b/samples/devbytes/ui/ListViewDeletion/AndroidManifest.xml
new file mode 100644
index 0000000..ba48e97
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.listviewdeletion"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.listviewdeletion.ListViewDeletion"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/ui/ListViewDeletion/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/ui/ListViewDeletion/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/ListViewDeletion/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/ui/ListViewDeletion/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/ListViewDeletion/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/ui/ListViewDeletion/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/ListViewDeletion/res/layout/activity_list_view_deletion.xml b/samples/devbytes/ui/ListViewDeletion/res/layout/activity_list_view_deletion.xml
new file mode 100644
index 0000000..4b98347
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/layout/activity_list_view_deletion.xml
@@ -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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ListViewAnimations" >
+
+    <CheckBox
+        android:id="@+id/usePositionsCB"
+        android:text="@string/use_positions"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <Button
+        android:id="@+id/deleteButton"
+        android:text="@string/delete_selected"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <ListView
+        android:id="@+id/listview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/samples/devbytes/ui/ListViewDeletion/res/values-v11/styles.xml b/samples/devbytes/ui/ListViewDeletion/res/values-v11/styles.xml
new file mode 100644
index 0000000..139d283
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/values-v11/styles.xml
@@ -0,0 +1,25 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/ui/ListViewDeletion/res/values-v14/styles.xml b/samples/devbytes/ui/ListViewDeletion/res/values-v14/styles.xml
new file mode 100644
index 0000000..8ac4522
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/values-v14/styles.xml
@@ -0,0 +1,26 @@
+<!-- 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>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/ui/ListViewDeletion/res/values/strings.xml b/samples/devbytes/ui/ListViewDeletion/res/values/strings.xml
new file mode 100644
index 0000000..7801d60
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!-- 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="app_name">ListViewDeletion</string>
+    <string name="use_positions">Use Positions</string>
+    <string name="delete_selected">Delete Selected</string>
+
+</resources>
diff --git a/samples/devbytes/ui/ListViewDeletion/res/values/styles.xml b/samples/devbytes/ui/ListViewDeletion/res/values/styles.xml
new file mode 100644
index 0000000..f9460a7
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/res/values/styles.xml
@@ -0,0 +1,34 @@
+<!-- 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>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
diff --git a/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/Cheeses.java b/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/Cheeses.java
new file mode 100644
index 0000000..4bdd858
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * 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.example.android.listviewdeletion;
+
+public class Cheeses {
+
+    public static final String[] sCheeseStrings = {
+            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+    };
+
+}
diff --git a/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/ListViewDeletion.java b/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/ListViewDeletion.java
new file mode 100644
index 0000000..d353823
--- /dev/null
+++ b/samples/devbytes/ui/ListViewDeletion/src/com/example/android/listviewdeletion/ListViewDeletion.java
@@ -0,0 +1,175 @@
+/*
+ * 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.example.android.listviewdeletion;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ListView;
+
+/**
+ * This example shows how animating ListView views can lead to artifacts if those views are
+ * recycled before you animate them.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=NewCSg2JKLk.
+ */
+public class ListViewDeletion extends Activity {
+
+    final ArrayList<View> mCheckedViews = new ArrayList<View>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list_view_deletion);
+
+        final Button deleteButton = (Button) findViewById(R.id.deleteButton);
+        final CheckBox usePositionsCB = (CheckBox) findViewById(R.id.usePositionsCB);
+        final ListView listview = (ListView) findViewById(R.id.listview);
+        final ArrayList<String> cheeseList = new ArrayList<String>();
+        for (int i = 0; i < Cheeses.sCheeseStrings.length; ++i) {
+            cheeseList.add(Cheeses.sCheeseStrings[i]);
+        }
+        final StableArrayAdapter adapter = new StableArrayAdapter(this,
+                android.R.layout.simple_list_item_multiple_choice, cheeseList);
+        listview.setAdapter(adapter);
+        listview.setItemsCanFocus(false);
+        listview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+
+        // Clicking the delete button fades out the currently selected views
+        deleteButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                SparseBooleanArray checkedItems = listview.getCheckedItemPositions();
+                int numCheckedItems = checkedItems.size();
+                for (int i = numCheckedItems - 1; i >= 0; --i) {
+                    if (!checkedItems.valueAt(i)) {
+                        continue;
+                    }
+                    int position = checkedItems.keyAt(i);
+                    final String item = adapter.getItem(position);
+                    if (!usePositionsCB.isChecked()) {
+                        // Remove the actual data after the time period that we're going to run
+                        // the fading animation
+                        v.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                adapter.remove(item);
+                            }
+                        }, 300);
+                    } else {
+                        // This is the correct way to do this: first wee whether the item is
+                        // actually visible, and don't bother animating it if it's not.
+                        // Next, get the view associated with the item at the time of deletion
+                        // (not some old view chosen when the item was clicked).
+                        mCheckedViews.clear();
+                        int positionOnScreen = position - listview.getFirstVisiblePosition();
+                        if (positionOnScreen >= 0 &&
+                                positionOnScreen < listview.getChildCount()) {
+                            final View view = listview.getChildAt(positionOnScreen);
+                            // All set to fade this view out. Using ViewPropertyAnimator accounts
+                            // for possible recycling of views during the animation itself
+                            // (see the ListViewAnimations example for more on this).
+                            view.animate().alpha(0).withEndAction(new Runnable() {
+                                @Override
+                                public void run() {
+                                    view.setAlpha(1);
+                                    adapter.remove(item);
+                                }
+                            });
+                        } else {
+                            // Not animating the view, but don't delete it yet to avoid making the
+                            // list shift due to offscreen deletions
+                            v.postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    adapter.remove(item);
+                                }
+                            }, 300);
+                        }
+                    }
+                }
+                // THIS IS THE WRONG WAY TO DO THIS
+                // We're basing our decision of the views to be animated based on outdated
+                // information at selection time. Then we're going ahead and running an animation
+                // on those views even when the selected items might not even be in view (in which
+                // case we'll probably be mistakenly fading out something else that is on the
+                // screen and is re-using that recycled view).
+                for (int i = 0; i < mCheckedViews.size(); ++i) {
+                    final View checkedView = mCheckedViews.get(i);
+                    checkedView.animate().alpha(0).withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            checkedView.setAlpha(1);
+                        }
+                    });
+                }
+                mCheckedViews.clear();
+                adapter.notifyDataSetChanged();
+            }
+        });
+
+        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
+            @Override
+            public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
+                boolean checked = listview.isItemChecked(position);
+                if (checked) {
+                    mCheckedViews.add(view);
+                } else {
+                    mCheckedViews.remove(view);
+                }
+            }
+        });
+    }
+
+    private class StableArrayAdapter extends ArrayAdapter<String> {
+
+        HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
+
+        public StableArrayAdapter(Context context, int textViewResourceId,
+                List<String> objects) {
+            super(context, textViewResourceId, objects);
+            for (int i = 0; i < objects.size(); ++i) {
+                mIdMap.put(objects.get(i), i);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            String item = getItem(position);
+            return mIdMap.get(item);
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+    }
+
+}
diff --git a/samples/devbytes/ui/RequestDuringLayout/AndroidManifest.xml b/samples/devbytes/ui/RequestDuringLayout/AndroidManifest.xml
new file mode 100644
index 0000000..219e014
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/AndroidManifest.xml
@@ -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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.requestduringlayout"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.example.android.requestduringlayout.RequestDuringLayout"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/ui/RequestDuringLayout/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..fba1ff0
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/ui/RequestDuringLayout/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..72a445d
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/ui/RequestDuringLayout/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..002e7b0
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/layout/activity_request_during_layout.xml b/samples/devbytes/ui/RequestDuringLayout/res/layout/activity_request_during_layout.xml
new file mode 100644
index 0000000..b026db8
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/layout/activity_request_during_layout.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+      class="com.android.requestduringlayout.RequestDuringLayout$MyLayout"
+      android:orientation="vertical"
+      android:id="@+id/container"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent" >
+
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/addView"
+                android:id="@+id/addView" />
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/removeView"
+                android:id="@+id/removeView" />
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/forceLayout"
+                android:id="@+id/forceLayout" />
+    </LinearLayout>
+
+</view>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values-large/dimens.xml b/samples/devbytes/ui/RequestDuringLayout/res/values-large/dimens.xml
new file mode 100644
index 0000000..59194f4
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values-large/dimens.xml
@@ -0,0 +1,21 @@
+<!-- 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="padding_small">8dp</dimen>
+    <dimen name="padding_medium">16dp</dimen>
+    <dimen name="padding_large">16dp</dimen>
+
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values-v11/styles.xml b/samples/devbytes/ui/RequestDuringLayout/res/values-v11/styles.xml
new file mode 100644
index 0000000..c16b77b
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values-v11/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values-v14/styles.xml b/samples/devbytes/ui/RequestDuringLayout/res/values-v14/styles.xml
new file mode 100644
index 0000000..0f1a1ef
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values-v14/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values/dimens.xml b/samples/devbytes/ui/RequestDuringLayout/res/values/dimens.xml
new file mode 100644
index 0000000..dbe215a
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!-- 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="padding_small">8dp</dimen>
+    <dimen name="padding_medium">8dp</dimen>
+    <dimen name="padding_large">16dp</dimen>
+
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values/strings.xml b/samples/devbytes/ui/RequestDuringLayout/res/values/strings.xml
new file mode 100644
index 0000000..6a5e6d1
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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="app_name">RequestDuringLayout</string>
+    <string name="button">Button</string>
+    <string name="menu_settings">Settings</string>
+    <string name="addView">Add</string>
+    <string name="removeView">Remove</string>
+    <string name="forceLayout">Layout</string>
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/res/values/styles.xml b/samples/devbytes/ui/RequestDuringLayout/res/values/styles.xml
new file mode 100644
index 0000000..de01fbd
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/res/values/styles.xml
@@ -0,0 +1,19 @@
+<!-- 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>
+
+    <style name="AppTheme" parent="android:Theme.Light" />
+
+</resources>
diff --git a/samples/devbytes/ui/RequestDuringLayout/src/com/example/android/requestduringlayout/RequestDuringLayout.java b/samples/devbytes/ui/RequestDuringLayout/src/com/example/android/requestduringlayout/RequestDuringLayout.java
new file mode 100644
index 0000000..e6f22cf
--- /dev/null
+++ b/samples/devbytes/ui/RequestDuringLayout/src/com/example/android/requestduringlayout/RequestDuringLayout.java
@@ -0,0 +1,131 @@
+/*
+ * 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.example.android.requestduringlayout;
+
+import com.android.requestduringlayout.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * This example shows what horrible things can result from calling requestLayout() during
+ * a layout pass. DON'T DO THIS.
+ *
+ * Watch the associated video for this demo on the DevBytes channel of developer.android.com
+ * or on YouTube at https://www.youtube.com/watch?v=HbAeTGoKG6k.
+ */
+public class RequestDuringLayout extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_request_during_layout);
+
+        final MyLayout myLayout = (MyLayout) findViewById(R.id.container);
+        Button addViewButton = (Button) findViewById(R.id.addView);
+        Button removeViewButton = (Button) findViewById(R.id.removeView);
+        Button forceLayoutButton = (Button) findViewById(R.id.forceLayout);
+
+        addViewButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                myLayout.mAddRequestPending = true;
+                myLayout.requestLayout();
+            }
+        });
+
+        removeViewButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                myLayout.mRemoveRequestPending = true;
+                myLayout.requestLayout();
+            }
+        });
+
+        forceLayoutButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                myLayout.requestLayout();
+            }
+        });
+
+    }
+
+    /**
+     * Custom layout to enable the convoluted way of requesting-during-layout that we're
+     * trying to show here. Yes, it's a hack. But it's a case that many apps hit (in much more
+     * complicated and less demoable ways), so it's interesting to at least understand the
+     * artifacts that come from this sequence of events.
+     */
+    static class MyLayout extends LinearLayout {
+
+        int numButtons = 0;
+        boolean mAddRequestPending = false;
+        boolean mRemoveRequestPending = false;
+
+        public MyLayout(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        public MyLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public MyLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            super.onLayout(changed, l, t, r, b);
+            // Here is the root of the problem: we are adding/removing views during layout. This
+            // means that this view and its container will be put into an uncertain state that
+            // can be difficult to discover and recover from.
+            // Better approach: just add/remove at a time when layout is not running, certainly not
+            // in the middle of onLayout(), or other layout-associated logic.
+            if (mRemoveRequestPending) {
+                removeButton();
+                mRemoveRequestPending = false;
+            }
+            if (mAddRequestPending) {
+                addButton();
+                mAddRequestPending = false;
+            }
+        }
+
+        private void removeButton() {
+            if (getChildCount() > 1) {
+                removeViewAt(1);
+            }
+        }
+
+        private void addButton() {
+            Button button = new Button(getContext());
+            button.setLayoutParams(new LayoutParams(
+                    LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            button.setText("Button " + (numButtons++));
+            addView(button);
+        }
+
+    }
+
+}
diff --git a/samples/training/ContactsList/AndroidManifest.xml b/samples/training/ContactsList/AndroidManifest.xml
new file mode 100644
index 0000000..025e9cf
--- /dev/null
+++ b/samples/training/ContactsList/AndroidManifest.xml
@@ -0,0 +1,72 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.contactslist"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="5"
+        android:targetSdkVersion="17" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application
+        android:description="@string/app_description"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme"
+        android:allowBackup="true">
+
+        <!-- When the soft keyboard is showing the views of this activity should be resized in the
+             remaining space so that inline searching can take place without having to dismiss the
+             keyboard to see all the content. Therefore windowSoftInputMode is set to
+             adjustResize. -->
+        <activity
+                android:name=".ui.ContactsListActivity"
+                android:label="@string/activity_contacts_list"
+                android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!-- Add intent-filter for search intent action and specify searchable configuration
+                 via meta-data tag. This allows this activity to receive search intents via the
+                 system hooks. In this sample this is only used on older OS versions (pre-Honeycomb)
+                 via the activity search dialog. See the Search API guide for more information:
+                 http://developer.android.com/guide/topics/search/search-dialog.html -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+            </intent-filter>
+            <meta-data android:name="android.app.searchable"
+                       android:resource="@xml/searchable_contacts" />
+        </activity>
+        <activity
+            android:name=".ui.ContactDetailActivity"
+            android:label="@string/activity_contact_detail"
+            android:parentActivityName=".ui.ContactsListActivity">
+            <!-- Define hierarchical parent of this activity, both via the system
+                 parentActivityName attribute (added in API Level 16) and via meta-data annotation.
+                 This allows use of the support library NavUtils class in a way that works over
+                 all Android versions. See the "Tasks and Back Stack" guide for more information:
+                 http://developer.android.com/guide/components/tasks-and-back-stack.html
+            -->
+            <meta-data android:name="android.support.PARENT_ACTIVITY"
+                       android:value=".ui.ContactsListActivity" />
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/training/ContactsList/libs/android-support-v4.jar b/samples/training/ContactsList/libs/android-support-v4.jar
new file mode 100644
index 0000000..65ebaf8
--- /dev/null
+++ b/samples/training/ContactsList/libs/android-support-v4.jar
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..0e4f334
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..9d4c934
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..6b7ce8d
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png
new file mode 100644
index 0000000..644d1c1
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png
new file mode 100644
index 0000000..d423b9e
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png
new file mode 100644
index 0000000..c233914
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..1ef4a82
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..38e4c30
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..4c0e35e
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2b3a35a
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
new file mode 100644
index 0000000..ee030fb
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
new file mode 100644
index 0000000..7140957
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..86097d8
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..71fb427
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..aad37b5
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png
new file mode 100644
index 0000000..d7ba6fe
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png
new file mode 100644
index 0000000..6a3afe2
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png
new file mode 100644
index 0000000..68499c5
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..6b3d131
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..0b52683
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..ead9718
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..1baf723
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png
new file mode 100644
index 0000000..b23e921
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png
new file mode 100644
index 0000000..38f14f7
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..1ebdb43
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..6f7e335
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..340031b
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png
new file mode 100644
index 0000000..b064476
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png
new file mode 100644
index 0000000..6f2eb59
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png
new file mode 100644
index 0000000..4aed873
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..c2b58df
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..f6fd172
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..05a65f6
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e0b49df
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e9e1527
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml b/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml
new file mode 100644
index 0000000..9e68152
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<!-- Refer to documentation for the <selector> element. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+            android:state_focused="false"
+            android:state_selected="false"
+            android:state_pressed="false"
+            android:drawable="@drawable/quickcontact_badge_small_unpressed"/>
+
+    <item
+            android:state_pressed="true"
+            android:drawable="@drawable/quickcontact_badge_small_pressed"/>
+
+</selector>
diff --git a/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml b/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml
new file mode 100644
index 0000000..a6dd381
--- /dev/null
+++ b/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml
@@ -0,0 +1,81 @@
+<?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:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:weightSum="100">
+
+        <ImageView
+                android:id="@+id/contact_image"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="@integer/contact_detail_photo_percent"
+                android:scaleType="centerCrop"
+                android:src="@drawable/ic_contact_picture_180_holo_light"
+                android:contentDescription="@string/imageview_description"/>
+
+        <LinearLayout
+                android:layout_height="match_parent"
+                android:layout_width="0dp"
+                android:layout_weight="@integer/contact_detail_info_percent"
+                android:orientation="vertical">
+
+            <TextView android:id="@+id/contact_name"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingLeft="@dimen/padding"
+                      android:paddingRight="@dimen/padding"
+                      android:paddingTop="@dimen/padding"
+                      android:visibility="gone"
+                      android:textAppearance="@style/contactNameTitle"/>
+
+            <ScrollView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                <LinearLayout
+                        android:id="@+id/contact_details_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingBottom="@dimen/padding"
+                        android:orientation="vertical">
+                </LinearLayout>
+
+            </ScrollView>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <!-- This view will be displayed when the views above are hidden. That happens when in two-pane
+         layout mode and no contact is currently selected and therefore the this fragment will
+         simply show a text message instead of contact details. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contact_selected"
+              android:fontFamily="sans-serif-light"
+              android:visibility="gone"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/activity_main.xml b/samples/training/ContactsList/res/layout/activity_main.xml
new file mode 100644
index 0000000..20fe26b
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/activity_main.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.
+-->
+
+<!-- Main Activity single pane layout. This layout contains a single ContactsListFragment that
+     displays a list of contacts. Tapping on a contact will start a new activity to display the
+     contact details. -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+          android:name="com.example.android.contactslist.ui.ContactsListFragment"
+          android:id="@+id/contact_list"
+          android:layout_height="match_parent"
+          android:layout_width="match_parent"/>
diff --git a/samples/training/ContactsList/res/layout/activity_main_twopanes.xml b/samples/training/ContactsList/res/layout/activity_main_twopanes.xml
new file mode 100644
index 0000000..d67f548
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/activity_main_twopanes.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+
+<!-- Main Activity two-pane layout. This layout has two panes, a ContactsListFragment on the left
+     and a ContactDetailFragment on the right. Tapping on a contact in the list loads the details
+     of that contact on the right. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="horizontal"
+              android:showDividers="middle"
+              android:divider="?android:attr/listDivider"
+              android:weightSum="100"
+              android:baselineAligned="false">
+
+    <fragment class="com.example.android.contactslist.ui.ContactsListFragment"
+              android:id="@+id/contact_list"
+              android:layout_height="match_parent"
+              android:layout_width="0dp"
+              android:layout_weight="@integer/contact_list_percent"/>
+
+    <fragment class="com.example.android.contactslist.ui.ContactDetailFragment"
+              android:id="@+id/contact_detail"
+              android:layout_height="match_parent"
+              android:layout_width="0dp"
+              android:layout_weight="@integer/contact_detail_percent"/>
+
+</LinearLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_detail_fragment.xml b/samples/training/ContactsList/res/layout/contact_detail_fragment.xml
new file mode 100644
index 0000000..1676e2e
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_detail_fragment.xml
@@ -0,0 +1,86 @@
+<?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.
+-->
+
+<!-- This layout is used by ContactDetailFragment to show contact details: contact photo, contact
+     display name and a dynamic number of addresses (if the contact has any) inside a ScrollView.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:weightSum="100">
+
+        <ImageView
+                android:id="@+id/contact_image"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="@integer/contact_detail_photo_percent"
+                android:scaleType="centerCrop"
+                android:src="@drawable/ic_contact_picture_180_holo_light"
+                android:contentDescription="@string/imageview_description"/>
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="@integer/contact_detail_info_percent"
+                android:orientation="vertical">
+
+            <TextView android:id="@+id/contact_name"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingLeft="@dimen/padding"
+                      android:paddingRight="@dimen/padding"
+                      android:paddingTop="@dimen/padding"
+                      android:visibility="gone"
+                      style="@style/contactNameTitle"/>
+
+            <ScrollView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                <LinearLayout
+                        android:id="@+id/contact_details_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingBottom="@dimen/padding"
+                        android:orientation="vertical">
+                </LinearLayout>
+
+            </ScrollView>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <!-- This view will be displayed when the views above are hidden. That happens when in two-pane
+         layout mode and no contact is currently selected and therefore the this fragment will
+         simply show a text message instead of contact details. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contact_selected"
+              android:fontFamily="sans-serif-light"
+              android:visibility="gone"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_detail_item.xml b/samples/training/ContactsList/res/layout/contact_detail_item.xml
new file mode 100644
index 0000000..b1e832a
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_detail_item.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.
+-->
+
+<!-- This layout is used to display a single mailing address for a contact. In the case of multiple
+     mailing addresses it could be inflated multiple times and displayed in a ScrollView container
+     to let the user more easily scroll over all addresses. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:paddingTop="@dimen/padding"
+              android:paddingLeft="@dimen/padding"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+            android:id="@+id/contact_detail_header"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            style="@style/addressHeader"/>
+
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:showDividers="middle"
+                  android:dividerPadding="12dp"
+                  android:minHeight="48dp"
+                  android:divider="?android:attr/listDivider">
+
+        <TextView
+                android:id="@+id/contact_detail_item"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:paddingRight="@dimen/padding"
+                android:layout_gravity="center"
+                style="@style/addressDetail"/>
+
+        <ImageButton
+            android:id="@+id/button_view_address"
+            android:src="@drawable/ic_action_map"
+            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp"
+            android:layout_gravity="center"
+            android:contentDescription="@string/address_button_description"
+            style="@style/addressButton"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_list_fragment.xml b/samples/training/ContactsList/res/layout/contact_list_fragment.xml
new file mode 100644
index 0000000..3fc2ae4
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_list_fragment.xml
@@ -0,0 +1,38 @@
+<?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">
+
+    <!-- Use standard android.R class list id instead of app specific id. This is just useful for
+         consistency. -->
+    <ListView android:id="@id/android:list"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              style="@style/ContactListView"/>
+
+    <!-- Use standard android.R class empty id instead of app specific id. This is just useful for
+     consistency. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contacts"
+              android:fontFamily="sans-serif-light"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_list_item.xml b/samples/training/ContactsList/res/layout/contact_list_item.xml
new file mode 100644
index 0000000..1ca24ad
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_list_item.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="?android:attr/listPreferredItemHeight"
+                style="@style/listViewActivatedStyle">
+
+    <!-- Use standard android.R class icon id instead of app specific id. This is just useful for
+         consistency. Use scaleType=centerCrop to give a nice full cropped image in the assigned
+         space -->
+    <QuickContactBadge android:id="@android:id/icon"
+                       android:layout_height="?android:attr/listPreferredItemHeight"
+                       android:layout_width="?android:attr/listPreferredItemHeight"
+                       android:scaleType="centerCrop"
+                       style="@style/quickContactBadgeStyle"
+                       android:src="@drawable/ic_contact_picture_holo_light"/>
+
+    <!-- Use standard android.R class text2 id instead of app specific id. This is just useful for
+         consistency. This is secondary text and not always visible so by default is has its
+         visibility set to gone -->
+    <TextView android:id="@android:id/text2"
+              android:paddingLeft="@dimen/listview_item_padding"
+              android:paddingRight="@dimen/listview_item_padding"
+              android:layout_width="match_parent"
+              android:layout_height="26dp"
+              android:layout_toRightOf="@android:id/icon"
+              android:layout_alignParentBottom="true"
+              android:layout_alignParentRight="true"
+              android:fontFamily="sans-serif"
+              android:singleLine="true"
+              android:ellipsize="marquee"
+              android:visibility="gone"
+              android:text="@string/search_match_other"
+              android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+    <!-- Use standard android.R class text1 id instead of app specific id. This is just useful for
+         consistency. This view also sets layout_alignWithParentIfMissing=true which lets the view
+         align with the parent view if the text2 view is not part of the view hierarchy (which is
+         its initial state). -->
+    <TextView android:id="@android:id/text1"
+              android:paddingLeft="@dimen/listview_item_padding"
+              android:paddingRight="@dimen/listview_item_padding"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_above="@android:id/text2"
+              android:layout_toRightOf="@android:id/icon"
+              android:gravity="center_vertical"
+              android:layout_alignParentRight="true"
+              android:layout_alignParentTop="true"
+              android:layout_alignWithParentIfMissing="true"
+              android:fontFamily="sans-serif-light"
+              android:singleLine="true"
+              android:ellipsize="marquee"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</RelativeLayout>
diff --git a/samples/training/ContactsList/res/menu/contact_detail_menu.xml b/samples/training/ContactsList/res/menu/contact_detail_menu.xml
new file mode 100644
index 0000000..f2c17df
--- /dev/null
+++ b/samples/training/ContactsList/res/menu/contact_detail_menu.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+            android:id="@+id/menu_edit_contact"
+            android:title="@string/menu_edit_contact"
+            android:icon="@drawable/ic_action_edit"
+            android:showAsAction="ifRoom"/>
+
+</menu>
diff --git a/samples/training/ContactsList/res/menu/contact_list_menu.xml b/samples/training/ContactsList/res/menu/contact_list_menu.xml
new file mode 100644
index 0000000..e784054
--- /dev/null
+++ b/samples/training/ContactsList/res/menu/contact_list_menu.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- The search menu item. Honeycomb and above uses an ActionView or specifically a SearchView
+         which expands within the Action Bar directly. Note the initial collapsed state set using
+         collapseActionView in the showAsAction attribute. -->
+    <item
+            android:id="@+id/menu_search"
+            android:title="@string/menu_search"
+            android:icon="@drawable/ic_action_search"
+            android:showAsAction="ifRoom|collapseActionView"
+            android:actionViewClass="android.widget.SearchView"/>
+
+    <item
+            android:id="@+id/menu_add_contact"
+            android:title="@string/menu_add_contact"
+            android:icon="@drawable/ic_action_add"
+            android:showAsAction="ifRoom"/>
+
+</menu>
diff --git a/samples/training/ContactsList/res/values-sw360dp/styles.xml b/samples/training/ContactsList/res/values-sw360dp/styles.xml
new file mode 100644
index 0000000..eb25832
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw360dp/styles.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.
+-->
+<resources>
+
+    <!-- This style bumps up the address details font size to large on devices that have a smallest
+         width of 360dp (larger phones). -->
+    <style name="addressDetail" parent="@android:style/TextAppearance.Large">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp-port/integers.xml b/samples/training/ContactsList/res/values-sw600dp-port/integers.xml
new file mode 100644
index 0000000..cb09a2b
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp-port/integers.xml
@@ -0,0 +1,24 @@
+<?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>
+
+    <!-- On devices with a smallest width of 600dp or more in portrait orientation, the two-pane
+         layout should allocate equal space to each fragment. -->
+    <integer name="contact_list_percent">50</integer>
+    <integer name="contact_detail_percent">50</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/bools.xml b/samples/training/ContactsList/res/values-sw600dp/bools.xml
new file mode 100644
index 0000000..69ce942
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/bools.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>
+
+    <!-- On devices with a smallest width of 600dp or more, switch to a two-pane layout.-->
+    <bool name="has_two_panes">true</bool>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/integers.xml b/samples/training/ContactsList/res/values-sw600dp/integers.xml
new file mode 100644
index 0000000..eef8547
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/integers.xml
@@ -0,0 +1,24 @@
+<?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>
+
+    <!-- On devices with a smallest width of 600dp or more, the two-pane layout should allocate
+         a larger portion of the screen to the detail fragment. -->
+    <integer name="contact_list_percent">35</integer>
+    <integer name="contact_detail_percent">65</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/layout.xml b/samples/training/ContactsList/res/values-sw600dp/layout.xml
new file mode 100644
index 0000000..c9f8848
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/layout.xml
@@ -0,0 +1,23 @@
+<?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>
+
+    <!-- Create a layout alias so that devices with a minimum width of 600dp or more will use the
+         two-pane layout when referring to the activity_main layout identifier. -->
+    <item name="activity_main" type="layout">@layout/activity_main_twopanes</item>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/styles.xml b/samples/training/ContactsList/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..980fd97
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/styles.xml
@@ -0,0 +1,25 @@
+<?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>
+
+    <style name="ContactListView">
+        <item name="android:verticalScrollbarPosition">left</item>
+        <item name="android:fastScrollAlwaysVisible">true</item>
+        <item name="android:scrollbarStyle">outsideInset</item>
+    </style>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw720dp/dimens.xml b/samples/training/ContactsList/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..2184cd0
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw720dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+
+    <!-- On devices with much larger screen sizes, such as a 10" tablet like Nexus 10, bump up the
+         common padding value to add some extra white space which makes the layouts feel more
+         suitable for the larger screen. -->
+    <dimen name="padding">32dp</dimen>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-v11/styles.xml b/samples/training/ContactsList/res/values-v11/styles.xml
new file mode 100644
index 0000000..d22ffc9
--- /dev/null
+++ b/samples/training/ContactsList/res/values-v11/styles.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<!-- API Level 11 and above specific resource files. Some of these styles allow us to use new
+     system styles or attributes introduced in API Level 11 and others allow overriding already
+     defined style that are only suitable for older OS versions (such as quickContactBadgeStyle).-->
+
+<resources>
+
+    <style name="AppTheme" parent="@android:style/Theme.Holo.Light"/>
+
+    <style name="listViewActivatedStyle">
+        <item name="android:background">?android:attr/activatedBackgroundIndicator</item>
+    </style>
+
+    <style name="quickContactBadgeStyle"/>
+
+    <style name="addressHeader" parent="@android:style/TextAppearance.Small">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@color/holo_blue</item>
+    </style>
+
+    <style name="addressButton" parent="@android:style/Widget.Holo.Button.Borderless"/>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/bools.xml b/samples/training/ContactsList/res/values/bools.xml
new file mode 100644
index 0000000..1197eaa
--- /dev/null
+++ b/samples/training/ContactsList/res/values/bools.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>
+
+    <!-- Default is to use a single pane layout -->
+    <bool name="has_two_panes">false</bool>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/colors.xml b/samples/training/ContactsList/res/values/colors.xml
new file mode 100644
index 0000000..e078dcf
--- /dev/null
+++ b/samples/training/ContactsList/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?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>
+
+    <!-- Define a standard holo blue color. Useful as we can refer to it from various other
+         resource files or even code and it only needs to be updated in one place if we wanted
+         to change it. -->
+    <color name="holo_blue">#FF33B5E5</color>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/dimens.xml b/samples/training/ContactsList/res/values/dimens.xml
new file mode 100644
index 0000000..25db89d
--- /dev/null
+++ b/samples/training/ContactsList/res/values/dimens.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.
+-->
+<resources>
+
+    <!-- Define some key view padding values. This is useful because the values can be used in
+         multiple views and changed from one central location. It also gives us the ability to
+         provide alternate values for different device configurations using resource directory
+         qualifiers. -->
+    <dimen name="padding">16dp</dimen>
+    <dimen name="listview_item_padding">16dp</dimen>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/integers.xml b/samples/training/ContactsList/res/values/integers.xml
new file mode 100644
index 0000000..10596a6
--- /dev/null
+++ b/samples/training/ContactsList/res/values/integers.xml
@@ -0,0 +1,24 @@
+<?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>
+
+    <!-- These are the default percent values that the contact photo and information should take up
+         in the ContactDetailFragment. -->
+    <integer name="contact_detail_photo_percent">45</integer>
+    <integer name="contact_detail_info_percent">55</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/strings.xml b/samples/training/ContactsList/res/values/strings.xml
new file mode 100644
index 0000000..3253e22
--- /dev/null
+++ b/samples/training/ContactsList/res/values/strings.xml
@@ -0,0 +1,48 @@
+<?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="app_name">Contacts List</string>
+    <string name="activity_contacts_list">Contacts List</string>
+    <string name="activity_contact_detail">Contact Detail</string>
+    <string name="contacts_list_search_results_title">Contacts List Search for \"%s\"</string>
+    <string name="app_description">This is a sample app, demonstrating use of the Android system Contacts Provider.</string>
+    <string name="imageview_description">Contact Thumbnail</string>
+    <string name="address_button_description">View Address</string>
+    <string name="menu_search">Search</string>
+    <string name="menu_add_contact">Add Contact</string>
+    <string name="menu_edit_contact">Edit Contact</string>
+    <string name="no_contacts">No Contacts Found</string>
+    <string name="no_contact_selected">No Contact Selected</string>
+    <string name="search_hint">Find contacts</string>
+
+    <!-- Used for the AlphabetIndexer in ContactsListFragment to provide quick navigation by
+         alphabet using ListView fast scroll. -->
+    <string name="alphabet">ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
+
+    <!-- When using ContactsContract.Contacts#CONTENT_FILTER_URI to search contacts, a match occurs
+         when using a number of different fields, such as name, e-mail address, address, phone
+         number, etc. When a match occurs that is not the name, there is currently no way to tell
+         which other field was matched. This string is displayed in the secondary display text in
+         ContactsListFragment when a search query match occurs that is not the display name.
+         -->
+    <string name="search_match_other">Matches Other Field</string>
+
+    <string name="no_address">No addresses found</string>
+    <string name="no_intent_found">No application found to handle this action</string>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/styles.xml b/samples/training/ContactsList/res/values/styles.xml
new file mode 100644
index 0000000..18d85f5
--- /dev/null
+++ b/samples/training/ContactsList/res/values/styles.xml
@@ -0,0 +1,61 @@
+<?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.
+-->
+
+<!-- This file defines various styles for the application. As this file is located in the /values
+     subdirectory the styles defined here will be used by default unless the styles are redefined
+     inside a more specific resource directory such as /values-sw600dp. -->
+
+<resources>
+
+    <style name="AppTheme" parent="@android:style/Theme"/>
+
+    <style name="ContactListView">
+        <item name="android:verticalScrollbarPosition">right</item>
+        <item name="android:fastScrollAlwaysVisible">true</item>
+        <item name="android:scrollbarStyle">outsideInset</item>
+    </style>
+
+    <style name="listViewActivatedStyle"/>
+
+    <style name="quickContactBadgeStyle">
+        <item name="android:background">@drawable/quickcontact_badge_small</item>
+    </style>
+
+    <style name="searchTextHiglight">
+        <item name="android:textColor">@color/holo_blue</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="addressHeader" parent="@android:style/TextAppearance.Small">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="addressDetail" parent="@android:style/TextAppearance.Medium">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+    <style name="contactNameTitle" parent="@android:style/TextAppearance.Large">
+        <item name="android:textSize">38sp</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+    <style name="addressButton"/>
+
+</resources>
diff --git a/samples/training/ContactsList/res/xml/searchable_contacts.xml b/samples/training/ContactsList/res/xml/searchable_contacts.xml
new file mode 100644
index 0000000..ce52eec
--- /dev/null
+++ b/samples/training/ContactsList/res/xml/searchable_contacts.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Define a searchable configuration. See the docs for more information:
+     http://developer.android.com/guide/topics/search/searchable-config.html -->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+            android:label="@string/app_name"
+            android:inputType="textPersonName"
+            android:hint="@string/search_hint"/>
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java
new file mode 100644
index 0000000..d53017f
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java
@@ -0,0 +1,91 @@
+/*
+ * 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.example.android.contactslist.ui;
+
+import android.annotation.TargetApi;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.NavUtils;
+import android.view.MenuItem;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.util.Utils;
+
+/**
+ * This class defines a simple FragmentActivity as the parent of {@link ContactDetailFragment}.
+ */
+public class ContactDetailActivity extends FragmentActivity {
+    // Defines a tag for identifying the single fragment that this activity holds
+    private static final String TAG = "ContactDetailActivity";
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (BuildConfig.DEBUG) {
+            // Enable strict mode checks when in debug modes
+            Utils.enableStrictMode();
+        }
+        super.onCreate(savedInstanceState);
+
+        // This activity expects to receive an intent that contains the uri of a contact
+        if (getIntent() != null) {
+
+            // For OS versions honeycomb and higher use action bar
+            if (Utils.hasHoneycomb()) {
+                // Enables action bar "up" navigation
+                getActionBar().setDisplayHomeAsUpEnabled(true);
+            }
+
+            // Fetch the data Uri from the intent provided to this activity
+            final Uri uri = getIntent().getData();
+
+            // Checks to see if fragment has already been added, otherwise adds a new
+            // ContactDetailFragment with the Uri provided in the intent
+            if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {
+                final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
+                // Adds a newly created ContactDetailFragment that is instantiated with the
+                // data Uri
+                ft.add(android.R.id.content, ContactDetailFragment.newInstance(uri), TAG);
+                ft.commit();
+            }
+        } else {
+            // No intent provided, nothing to do so finish()
+            finish();
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // Tapping on top left ActionBar icon navigates "up" to hierarchical parent screen.
+                // The parent is defined in the AndroidManifest entry for this activity via the
+                // parentActivityName attribute (and via meta-data tag for OS versions before API
+                // Level 16). See the "Tasks and Back Stack" guide for more information:
+                // http://developer.android.com/guide/components/tasks-and-back-stack.html
+                NavUtils.navigateUpFromSameTask(this);
+                return true;
+        }
+        // Otherwise, pass the item to the super implementation for handling, as described in the
+        // documentation.
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java
new file mode 100644
index 0000000..94d477c
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java
@@ -0,0 +1,687 @@
+/*
+ * 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.example.android.contactslist.ui;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.provider.ContactsContract.Data;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.util.DisplayMetrics;
+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.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.ImageLoader;
+import com.example.android.contactslist.util.Utils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * This fragment displays details of a specific contact from the contacts provider. It shows the
+ * contact's display photo, name and all its mailing addresses. You can also modify this fragment
+ * to show other information, such as phone numbers, email addresses and so forth.
+ *
+ * This fragment appears full-screen in an activity on devices with small screen sizes, and as
+ * part of a two-pane layout on devices with larger screens, alongside the
+ * {@link ContactsListFragment}.
+ *
+ * To create an instance of this fragment, use the factory method
+ * {@link ContactDetailFragment#newInstance(android.net.Uri)}, passing as an argument the contact
+ * Uri for the contact you want to display.
+ */
+public class ContactDetailFragment extends Fragment implements
+        LoaderManager.LoaderCallbacks<Cursor> {
+
+    public static final String EXTRA_CONTACT_URI =
+            "com.example.android.contactslist.ui.EXTRA_CONTACT_URI";
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactDetailFragment";
+
+    // The geo Uri scheme prefix, used with Intent.ACTION_VIEW to form a geographical address
+    // intent that will trigger available apps to handle viewing a location (such as Maps)
+    private static final String GEO_URI_SCHEME_PREFIX = "geo:0,0?q=";
+
+    // Whether or not this fragment is showing in a two pane layout
+    private boolean mIsTwoPaneLayout;
+
+    private Uri mContactUri; // Stores the contact Uri for this fragment instance
+    private ImageLoader mImageLoader; // Handles loading the contact image in a background thread
+
+    // Used to store references to key views, layouts and menu items as these need to be updated
+    // in multiple methods throughout this class.
+    private ImageView mImageView;
+    private LinearLayout mDetailsLayout;
+    private TextView mEmptyView;
+    private TextView mContactName;
+    private MenuItem mEditContactMenuItem;
+
+    /**
+     * Factory method to generate a new instance of the fragment given a contact Uri. A factory
+     * method is preferable to simply using the constructor as it handles creating the bundle and
+     * setting the bundle as an argument.
+     *
+     * @param contactUri The contact Uri to load
+     * @return A new instance of {@link ContactDetailFragment}
+     */
+    public static ContactDetailFragment newInstance(Uri contactUri) {
+        // Create new instance of this fragment
+        final ContactDetailFragment fragment = new ContactDetailFragment();
+
+        // Create and populate the args bundle
+        final Bundle args = new Bundle();
+        args.putParcelable(EXTRA_CONTACT_URI, contactUri);
+
+        // Assign the args bundle to the new fragment
+        fragment.setArguments(args);
+
+        // Return fragment
+        return fragment;
+    }
+
+    /**
+     * Fragments require an empty constructor.
+     */
+    public ContactDetailFragment() {}
+
+    /**
+     * Sets the contact that this Fragment displays, or clears the display if the contact argument
+     * is null. This will re-initialize all the views and start the queries to the system contacts
+     * provider to populate the contact information.
+     *
+     * @param contactLookupUri The contact lookup Uri to load and display in this fragment. Passing
+     *                         null is valid and the fragment will display a message that no
+     *                         contact is currently selected instead.
+     */
+    public void setContact(Uri contactLookupUri) {
+
+        // In version 3.0 and later, stores the provided contact lookup Uri in a class field. This
+        // Uri is then used at various points in this class to map to the provided contact.
+        if (Utils.hasHoneycomb()) {
+            mContactUri = contactLookupUri;
+        } else {
+            // For versions earlier than Android 3.0, stores a contact Uri that's constructed from
+            // contactLookupUri. Later on, the resulting Uri is combined with
+            // Contacts.Data.CONTENT_DIRECTORY to map to the provided contact. It's done
+            // differently for these earlier versions because Contacts.Data.CONTENT_DIRECTORY works
+            // differently for Android versions before 3.0.
+            mContactUri = Contacts.lookupContact(getActivity().getContentResolver(),
+                    contactLookupUri);
+        }
+
+        // If the Uri contains data, load the contact's image and load contact details.
+        if (contactLookupUri != null) {
+            // Asynchronously loads the contact image
+            mImageLoader.loadImage(mContactUri, mImageView);
+
+            // Shows the contact photo ImageView and hides the empty view
+            mImageView.setVisibility(View.VISIBLE);
+            mEmptyView.setVisibility(View.GONE);
+
+            // Shows the edit contact action/menu item
+            if (mEditContactMenuItem != null) {
+                mEditContactMenuItem.setVisible(true);
+            }
+
+            // Starts two queries to to retrieve contact information from the Contacts Provider.
+            // restartLoader() is used instead of initLoader() as this method may be called
+            // multiple times.
+            getLoaderManager().restartLoader(ContactDetailQuery.QUERY_ID, null, this);
+            getLoaderManager().restartLoader(ContactAddressQuery.QUERY_ID, null, this);
+        } else {
+            // If contactLookupUri is null, then the method was called when no contact was selected
+            // in the contacts list. This should only happen in a two-pane layout when the user
+            // hasn't yet selected a contact. Don't display an image for the contact, and don't
+            // account for the view's space in the layout. Turn on the TextView that appears when
+            // the layout is empty, and set the contact name to the empty string. Turn off any menu
+            // items that are visible.
+            mImageView.setVisibility(View.GONE);
+            mEmptyView.setVisibility(View.VISIBLE);
+            mDetailsLayout.removeAllViews();
+            if (mContactName != null) {
+                mContactName.setText("");
+            }
+            if (mEditContactMenuItem != null) {
+                mEditContactMenuItem.setVisible(false);
+            }
+        }
+    }
+
+    /**
+     * When the Fragment is first created, this callback is invoked. It initializes some key
+     * class fields.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if this fragment is part of a two pane set up or a single pane
+        mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Let this fragment contribute menu items
+        setHasOptionsMenu(true);
+
+        /*
+         * The ImageLoader takes care of loading and resizing images asynchronously into the
+         * ImageView. More thorough sample code demonstrating background image loading as well as
+         * details on how it works can be found in the following Android Training class:
+         * http://developer.android.com/training/displaying-bitmaps/
+         */
+        mImageLoader = new ImageLoader(getActivity(), getLargestScreenDimension()) {
+            @Override
+            protected Bitmap processBitmap(Object data) {
+                // This gets called in a background thread and passed the data from
+                // ImageLoader.loadImage().
+                return loadContactPhoto((Uri) data, getImageSize());
+
+            }
+        };
+
+        // Set a placeholder loading image for the image loader
+        mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_180_holo_light);
+
+        // Tell the image loader to set the image directly when it's finished loading
+        // rather than fading in
+        mImageLoader.setImageFadeIn(false);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+
+        // Inflates the main layout to be used by this fragment
+        final View detailView =
+                inflater.inflate(R.layout.contact_detail_fragment, container, false);
+
+        // Gets handles to view objects in the layout
+        mImageView = (ImageView) detailView.findViewById(R.id.contact_image);
+        mDetailsLayout = (LinearLayout) detailView.findViewById(R.id.contact_details_layout);
+        mEmptyView = (TextView) detailView.findViewById(android.R.id.empty);
+
+        if (mIsTwoPaneLayout) {
+            // If this is a two pane view, the following code changes the visibility of the contact
+            // name in details. For a one-pane view, the contact name is displayed as a title.
+            mContactName = (TextView) detailView.findViewById(R.id.contact_name);
+            mContactName.setVisibility(View.VISIBLE);
+        }
+
+        return detailView;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        // If not being created from a previous state
+        if (savedInstanceState == null) {
+            // Sets the argument extra as the currently displayed contact
+            setContact(getArguments() != null ?
+                    (Uri) getArguments().getParcelable(EXTRA_CONTACT_URI) : null);
+        } else {
+            // If being recreated from a saved state, sets the contact from the incoming
+            // savedInstanceState Bundle
+            setContact((Uri) savedInstanceState.getParcelable(EXTRA_CONTACT_URI));
+        }
+    }
+
+    /**
+     * When the Fragment is being saved in order to change activity state, save the
+     * currently-selected contact.
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        // Saves the contact Uri
+        outState.putParcelable(EXTRA_CONTACT_URI, mContactUri);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            // When "edit" menu option selected
+            case R.id.menu_edit_contact:
+                // Standard system edit contact intent
+                Intent intent = new Intent(Intent.ACTION_EDIT, mContactUri);
+
+                // Because of an issue in Android 4.0 (API level 14), clicking Done or Back in the
+                // People app doesn't return the user to your app; instead, it displays the People
+                // app's contact list. A workaround, introduced in Android 4.0.3 (API level 15) is
+                // to set a special flag in the extended data for the Intent you send to the People
+                // app. The issue is does not appear in versions prior to Android 4.0. You can use
+                // the flag with any version of the People app; if the workaround isn't needed,
+                // the flag is ignored.
+                intent.putExtra("finishActivityOnSaveCompleted", true);
+
+                // Start the edit activity
+                startActivity(intent);
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+
+        // Inflates the options menu for this fragment
+        inflater.inflate(R.menu.contact_detail_menu, menu);
+
+        // Gets a handle to the "find" menu item
+        mEditContactMenuItem = menu.findItem(R.id.menu_edit_contact);
+
+        // If contactUri is null the edit menu item should be hidden, otherwise
+        // it is visible.
+        mEditContactMenuItem.setVisible(mContactUri != null);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            // Two main queries to load the required information
+            case ContactDetailQuery.QUERY_ID:
+                // This query loads main contact details, see
+                // ContactDetailQuery for more information.
+                return new CursorLoader(getActivity(), mContactUri,
+                        ContactDetailQuery.PROJECTION,
+                        null, null, null);
+            case ContactAddressQuery.QUERY_ID:
+                // This query loads contact address details, see
+                // ContactAddressQuery for more information.
+                final Uri uri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY);
+                return new CursorLoader(getActivity(), uri,
+                        ContactAddressQuery.PROJECTION,
+                        ContactAddressQuery.SELECTION,
+                        null, null);
+        }
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+
+        // If this fragment was cleared while the query was running
+        // eg. from from a call like setContact(uri) then don't do
+        // anything.
+        if (mContactUri == null) {
+            return;
+        }
+
+        switch (loader.getId()) {
+            case ContactDetailQuery.QUERY_ID:
+                // Moves to the first row in the Cursor
+                if (data.moveToFirst()) {
+                    // For the contact details query, fetches the contact display name.
+                    // ContactDetailQuery.DISPLAY_NAME maps to the appropriate display
+                    // name field based on OS version.
+                    final String contactName = data.getString(ContactDetailQuery.DISPLAY_NAME);
+                    if (mIsTwoPaneLayout && mContactName != null) {
+                        // In the two pane layout, there is a dedicated TextView
+                        // that holds the contact name.
+                        mContactName.setText(contactName);
+                    } else {
+                        // In the single pane layout, sets the activity title
+                        // to the contact name. On HC+ this will be set as
+                        // the ActionBar title text.
+                        getActivity().setTitle(contactName);
+                    }
+                }
+                break;
+            case ContactAddressQuery.QUERY_ID:
+                // This query loads the contact address details. More than
+                // one contact address is possible, so move each one to a
+                // LinearLayout in a Scrollview so multiple addresses can
+                // be scrolled by the user.
+
+                // Each LinearLayout has the same LayoutParams so this can
+                // be created once and used for each address.
+                final LinearLayout.LayoutParams layoutParams =
+                        new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                ViewGroup.LayoutParams.WRAP_CONTENT);
+
+                // Clears out the details layout first in case the details
+                // layout has addresses from a previous data load still
+                // added as children.
+                mDetailsLayout.removeAllViews();
+
+                // Loops through all the rows in the Cursor
+                if (data.moveToFirst()) {
+                    do {
+                        // Builds the address layout
+                        final LinearLayout layout = buildAddressLayout(
+                                data.getInt(ContactAddressQuery.TYPE),
+                                data.getString(ContactAddressQuery.LABEL),
+                                data.getString(ContactAddressQuery.ADDRESS));
+                        // Adds the new address layout to the details layout
+                        mDetailsLayout.addView(layout, layoutParams);
+                    } while (data.moveToNext());
+                } else {
+                    // If nothing found, adds an empty address layout
+                    mDetailsLayout.addView(buildEmptyAddressLayout(), layoutParams);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // Nothing to do here. The Cursor does not need to be released as it was never directly
+        // bound to anything (like an adapter).
+    }
+
+    /**
+     * Builds an empty address layout that just shows that no addresses
+     * were found for this contact.
+     *
+     * @return A LinearLayout to add to the contact details layout
+     */
+    private LinearLayout buildEmptyAddressLayout() {
+        return buildAddressLayout(0, null, null);
+    }
+
+    /**
+     * Builds an address LinearLayout based on address information from the Contacts Provider.
+     * Each address for the contact gets its own LinearLayout object; for example, if the contact
+     * has three postal addresses, then 3 LinearLayouts are generated.
+     *
+     * @param addressType From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#TYPE}
+     * @param addressTypeLabel From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#LABEL}
+     * @param address From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#FORMATTED_ADDRESS}
+     * @return A LinearLayout to add to the contact details layout,
+     *         populated with the provided address details.
+     */
+    private LinearLayout buildAddressLayout(int addressType, String addressTypeLabel,
+            final String address) {
+
+        // Inflates the address layout
+        final LinearLayout addressLayout =
+                (LinearLayout) LayoutInflater.from(getActivity()).inflate(
+                        R.layout.contact_detail_item, mDetailsLayout, false);
+
+        // Gets handles to the view objects in the layout
+        final TextView headerTextView =
+                (TextView) addressLayout.findViewById(R.id.contact_detail_header);
+        final TextView addressTextView =
+                (TextView) addressLayout.findViewById(R.id.contact_detail_item);
+        final ImageButton viewAddressButton =
+                (ImageButton) addressLayout.findViewById(R.id.button_view_address);
+
+        // If there's no addresses for the contact, shows the empty view and message, and hides the
+        // header and button.
+        if (addressTypeLabel == null && addressType == 0) {
+            headerTextView.setVisibility(View.GONE);
+            viewAddressButton.setVisibility(View.GONE);
+            addressTextView.setText(R.string.no_address);
+        } else {
+            // Gets postal address label type
+            CharSequence label =
+                    StructuredPostal.getTypeLabel(getResources(), addressType, addressTypeLabel);
+
+            // Sets TextView objects in the layout
+            headerTextView.setText(label);
+            addressTextView.setText(address);
+
+            // Defines an onClickListener object for the address button
+            viewAddressButton.setOnClickListener(new View.OnClickListener() {
+                // Defines what to do when users click the address button
+                @Override
+                public void onClick(View view) {
+
+                    final Intent viewIntent =
+                            new Intent(Intent.ACTION_VIEW, constructGeoUri(address));
+
+                    // A PackageManager instance is needed to verify that there's a default app
+                    // that handles ACTION_VIEW and a geo Uri.
+                    final PackageManager packageManager = getActivity().getPackageManager();
+
+                    // Checks for an activity that can handle this intent. Preferred in this
+                    // case over Intent.createChooser() as it will still let the user choose
+                    // a default (or use a previously set default) for geo Uris.
+                    if (packageManager.resolveActivity(
+                            viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
+                        startActivity(viewIntent);
+                    } else {
+                        // If no default is found, displays a message that no activity can handle
+                        // the view button.
+                        Toast.makeText(getActivity(),
+                                R.string.no_intent_found, Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+
+        }
+        return addressLayout;
+    }
+
+    /**
+     * Constructs a geo scheme Uri from a postal address.
+     *
+     * @param postalAddress A postal address.
+     * @return the geo:// Uri for the postal address.
+     */
+    private Uri constructGeoUri(String postalAddress) {
+        // Concatenates the geo:// prefix to the postal address. The postal address must be
+        // converted to Uri format and encoded for special characters.
+        return Uri.parse(GEO_URI_SCHEME_PREFIX + Uri.encode(postalAddress));
+    }
+
+    /**
+     * Fetches the width or height of the screen in pixels, whichever is larger. This is used to
+     * set a maximum size limit on the contact photo that is retrieved from the Contacts Provider.
+     * This limit prevents the app from trying to decode and load an image that is much larger than
+     * the available screen area.
+     *
+     * @return The largest screen dimension in pixels.
+     */
+    private int getLargestScreenDimension() {
+        // Gets a DisplayMetrics object, which is used to retrieve the display's pixel height and
+        // width
+        final DisplayMetrics displayMetrics = new DisplayMetrics();
+
+        // Retrieves a displayMetrics object for the device's default display
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+        final int height = displayMetrics.heightPixels;
+        final int width = displayMetrics.widthPixels;
+
+        // Returns the larger of the two values
+        return height > width ? height : width;
+    }
+
+    /**
+     * Decodes and returns the contact's thumbnail image.
+     * @param contactUri The Uri of the contact containing the image.
+     * @param imageSize The desired target width and height of the output image in pixels.
+     * @return If a thumbnail image exists for the contact, a Bitmap image, otherwise null.
+     */
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private Bitmap loadContactPhoto(Uri contactUri, int imageSize) {
+
+        // Ensures the Fragment is still added to an activity. As this method is called in a
+        // background thread, there's the possibility the Fragment is no longer attached and
+        // added to an activity. If so, no need to spend resources loading the contact photo.
+        if (!isAdded() || getActivity() == null) {
+            return null;
+        }
+
+        // Instantiates a ContentResolver for retrieving the Uri of the image
+        final ContentResolver contentResolver = getActivity().getContentResolver();
+
+        // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
+        // ContentResolver can return an AssetFileDescriptor for the file.
+        AssetFileDescriptor afd = null;
+
+        if (Utils.hasICS()) {
+            // On platforms running Android 4.0 (API version 14) and later, a high resolution image
+            // is available from Photo.DISPLAY_PHOTO.
+            try {
+                // Constructs the content Uri for the image
+                Uri displayImageUri = Uri.withAppendedPath(contactUri, Photo.DISPLAY_PHOTO);
+
+                // Retrieves an AssetFileDescriptor from the Contacts Provider, using the
+                // constructed Uri
+                afd = contentResolver.openAssetFileDescriptor(displayImageUri, "r");
+                // If the file exists
+                if (afd != null) {
+                    // Reads and decodes the file to a Bitmap and scales it to the desired size
+                    return ImageLoader.decodeSampledBitmapFromDescriptor(
+                            afd.getFileDescriptor(), imageSize, imageSize);
+                }
+            } catch (FileNotFoundException e) {
+                // Catches file not found exceptions
+                if (BuildConfig.DEBUG) {
+                    // Log debug message, this is not an error message as this exception is thrown
+                    // when a contact is legitimately missing a contact photo (which will be quite
+                    // frequently in a long contacts list).
+                    Log.d(TAG, "Contact photo not found for contact " + contactUri.toString()
+                            + ": " + e.toString());
+                }
+            } finally {
+                // Once the decode is complete, this closes the file. You must do this each time
+                // you access an AssetFileDescriptor; otherwise, every image load you do will open
+                // a new descriptor.
+                if (afd != null) {
+                    try {
+                        afd.close();
+                    } catch (IOException e) {
+                        // Closing a file descriptor might cause an IOException if the file is
+                        // already closed. Nothing extra is needed to handle this.
+                    }
+                }
+            }
+        }
+
+        // If the platform version is less than Android 4.0 (API Level 14), use the only available
+        // image URI, which points to a normal-sized image.
+        try {
+            // Constructs the image Uri from the contact Uri and the directory twig from the
+            // Contacts.Photo table
+            Uri imageUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
+
+            // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed
+            // Uri
+            afd = getActivity().getContentResolver().openAssetFileDescriptor(imageUri, "r");
+
+            // If the file exists
+            if (afd != null) {
+                // Reads the image from the file, decodes it, and scales it to the available screen
+                // area
+                return ImageLoader.decodeSampledBitmapFromDescriptor(
+                        afd.getFileDescriptor(), imageSize, imageSize);
+            }
+        } catch (FileNotFoundException e) {
+            // Catches file not found exceptions
+            if (BuildConfig.DEBUG) {
+                // Log debug message, this is not an error message as this exception is thrown
+                // when a contact is legitimately missing a contact photo (which will be quite
+                // frequently in a long contacts list).
+                Log.d(TAG, "Contact photo not found for contact " + contactUri.toString()
+                        + ": " + e.toString());
+            }
+        } finally {
+            // Once the decode is complete, this closes the file. You must do this each time you
+            // access an AssetFileDescriptor; otherwise, every image load you do will open a new
+            // descriptor.
+            if (afd != null) {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    // Closing a file descriptor might cause an IOException if the file is
+                    // already closed. Ignore this.
+                }
+            }
+        }
+
+        // If none of the case selectors match, returns null.
+        return null;
+    }
+
+    /**
+     * This interface defines constants used by contact retrieval queries.
+     */
+    public interface ContactDetailQuery {
+        // A unique query ID to distinguish queries being run by the
+        // LoaderManager.
+        final static int QUERY_ID = 1;
+
+        // The query projection (columns to fetch from the provider)
+        @SuppressLint("InlinedApi")
+        final static String[] PROJECTION = {
+                Contacts._ID,
+                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
+        };
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int DISPLAY_NAME = 1;
+    }
+
+    /**
+     * This interface defines constants used by address retrieval queries.
+     */
+    public interface ContactAddressQuery {
+        // A unique query ID to distinguish queries being run by the
+        // LoaderManager.
+        final static int QUERY_ID = 2;
+
+        // The query projection (columns to fetch from the provider)
+        final static String[] PROJECTION = {
+                StructuredPostal._ID,
+                StructuredPostal.FORMATTED_ADDRESS,
+                StructuredPostal.TYPE,
+                StructuredPostal.LABEL,
+        };
+
+        // The query selection criteria. In this case matching against the
+        // StructuredPostal content mime type.
+        final static String SELECTION =
+                Data.MIMETYPE + "='" + StructuredPostal.CONTENT_ITEM_TYPE + "'";
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int ADDRESS = 1;
+        final static int TYPE = 2;
+        final static int LABEL = 3;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java
new file mode 100644
index 0000000..3a6cab2
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java
@@ -0,0 +1,130 @@
+/*
+ * 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.example.android.contactslist.ui;
+
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.Utils;
+
+/**
+ * FragmentActivity to hold the main {@link ContactsListFragment}. On larger screen devices which
+ * can fit two panes also load {@link ContactDetailFragment}.
+ */
+public class ContactsListActivity extends FragmentActivity implements
+        ContactsListFragment.OnContactsInteractionListener {
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactsListActivity";
+
+    private ContactDetailFragment mContactDetailFragment;
+
+    // If true, this is a larger screen device which fits two panes
+    private boolean isTwoPaneLayout;
+
+    // True if this activity instance is a search result view (used on pre-HC devices that load
+    // search results in a separate instance of the activity rather than loading results in-line
+    // as the query is typed.
+    private boolean isSearchResultView = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (BuildConfig.DEBUG) {
+            Utils.enableStrictMode();
+        }
+        super.onCreate(savedInstanceState);
+
+        // Set main content view. On smaller screen devices this is a single pane view with one
+        // fragment. One larger screen devices this is a two pane view with two fragments.
+        setContentView(R.layout.activity_main);
+
+        // Check if two pane bool is set based on resource directories
+        isTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Check if this activity instance has been triggered as a result of a search query. This
+        // will only happen on pre-HC OS versions as from HC onward search is carried out using
+        // an ActionBar SearchView which carries out the search in-line without loading a new
+        // Activity.
+        if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
+
+            // Fetch query from intent and notify the fragment that it should display search
+            // results instead of all contacts.
+            String searchQuery = getIntent().getStringExtra(SearchManager.QUERY);
+            ContactsListFragment mContactsListFragment = (ContactsListFragment)
+                    getSupportFragmentManager().findFragmentById(R.id.contact_list);
+
+            // This flag notes that the Activity is doing a search, and so the result will be
+            // search results rather than all contacts. This prevents the Activity and Fragment
+            // from trying to a search on search results.
+            isSearchResultView = true;
+            mContactsListFragment.setSearchQuery(searchQuery);
+
+            // Set special title for search results
+            String title = getString(R.string.contacts_list_search_results_title, searchQuery);
+            setTitle(title);
+        }
+
+        if (isTwoPaneLayout) {
+            // If two pane layout, locate the contact detail fragment
+            mContactDetailFragment = (ContactDetailFragment)
+                    getSupportFragmentManager().findFragmentById(R.id.contact_detail);
+        }
+    }
+
+    /**
+     * This interface callback lets the main contacts list fragment notify
+     * this activity that a contact has been selected.
+     *
+     * @param contactUri The contact Uri to the selected contact.
+     */
+    @Override
+    public void onContactSelected(Uri contactUri) {
+        if (isTwoPaneLayout && mContactDetailFragment != null) {
+            // If two pane layout then update the detail fragment to show the selected contact
+            mContactDetailFragment.setContact(contactUri);
+        } else {
+            // Otherwise single pane layout, start a new ContactDetailActivity with
+            // the contact Uri
+            Intent intent = new Intent(this, ContactDetailActivity.class);
+            intent.setData(contactUri);
+            startActivity(intent);
+        }
+    }
+
+    /**
+     * This interface callback lets the main contacts list fragment notify
+     * this activity that a contact is no longer selected.
+     */
+    @Override
+    public void onSelectionCleared() {
+        if (isTwoPaneLayout && mContactDetailFragment != null) {
+            mContactDetailFragment.setContact(null);
+        }
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        // Don't allow another search if this activity instance is already showing
+        // search results. Only used pre-HC.
+        return !isSearchResultView && super.onSearchRequested();
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java
new file mode 100644
index 0000000..c3a8a66
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java
@@ -0,0 +1,935 @@
+/*
+ * 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.example.android.contactslist.ui;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+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.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AlphabetIndexer;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+import android.widget.SearchView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.ImageLoader;
+import com.example.android.contactslist.util.Utils;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * This fragment displays a list of contacts stored in the Contacts Provider. Each item in the list
+ * shows the contact's thumbnail photo and display name. On devices with large screens, this
+ * fragment's UI appears as part of a two-pane layout, along with the UI of
+ * {@link ContactDetailFragment}. On smaller screens, this fragment's UI appears as a single pane.
+ *
+ * This Fragment retrieves contacts based on a search string. If the user doesn't enter a search
+ * string, then the list contains all the contacts in the Contacts Provider. If the user enters a
+ * search string, then the list contains only those contacts whose data matches the string. The
+ * Contacts Provider itself controls the matching algorithm, which is a "substring" search: if the
+ * search string is a substring of any of the contacts data, then there is a match.
+ *
+ * On newer API platforms, the search is implemented in a SearchView in the ActionBar; as the user
+ * types the search string, the list automatically refreshes to display results ("type to filter").
+ * On older platforms, the user must enter the full string and trigger the search. In response, the
+ * trigger starts a new Activity which loads a fresh instance of this fragment. The resulting UI
+ * displays the filtered list and disables the search feature to prevent furthering searching.
+ */
+public class ContactsListFragment extends ListFragment implements
+        AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactsListFragment";
+
+    // Bundle key for saving previously selected search result item
+    private static final String STATE_PREVIOUSLY_SELECTED_KEY =
+            "com.example.android.contactslist.ui.SELECTED_ITEM";
+
+    private ContactsAdapter mAdapter; // The main query adapter
+    private ImageLoader mImageLoader; // Handles loading the contact image in a background thread
+    private String mSearchTerm; // Stores the current search query term
+
+    // Contact selected listener that allows the activity holding this fragment to be notified of
+    // a contact being selected
+    private OnContactsInteractionListener mOnContactSelectedListener;
+
+    // Stores the previously selected search item so that on a configuration change the same item
+    // can be reselected again
+    private int mPreviouslySelectedSearchItem = 0;
+
+    // Whether or not the search query has changed since the last time the loader was refreshed
+    private boolean mSearchQueryChanged;
+
+    // Whether or not this fragment is showing in a two-pane layout
+    private boolean mIsTwoPaneLayout;
+
+    // Whether or not this is a search result view of this fragment, only used on pre-honeycomb
+    // OS versions as search results are shown in-line via Action Bar search from honeycomb onward
+    private boolean mIsSearchResultView = false;
+
+    /**
+     * Fragments require an empty constructor.
+     */
+    public ContactsListFragment() {}
+
+    /**
+     * In platform versions prior to Android 3.0, the ActionBar and SearchView are not supported,
+     * and the UI gets the search string from an EditText. However, the fragment doesn't allow
+     * another search when search results are already showing. This would confuse the user, because
+     * the resulting search would re-query the Contacts Provider instead of searching the listed
+     * results. This method sets the search query and also a boolean that tracks if this Fragment
+     * should be displayed as a search result view or not.
+     *
+     * @param query The contacts search query.
+     */
+    public void setSearchQuery(String query) {
+        if (TextUtils.isEmpty(query)) {
+            mIsSearchResultView = false;
+        } else {
+            mSearchTerm = query;
+            mIsSearchResultView = true;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if this fragment is part of a two-pane set up or a single pane by reading a
+        // boolean from the application resource directories. This lets allows us to easily specify
+        // which screen sizes should use a two-pane layout by setting this boolean in the
+        // corresponding resource size-qualified directory.
+        mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Let this fragment contribute menu items
+        setHasOptionsMenu(true);
+
+        // Create the main contacts adapter
+        mAdapter = new ContactsAdapter(getActivity());
+
+        if (savedInstanceState != null) {
+            // If we're restoring state after this fragment was recreated then
+            // retrieve previous search term and previously selected search
+            // result.
+            mSearchTerm = savedInstanceState.getString(SearchManager.QUERY);
+            mPreviouslySelectedSearchItem =
+                    savedInstanceState.getInt(STATE_PREVIOUSLY_SELECTED_KEY, 0);
+        }
+
+        /*
+         * An ImageLoader object loads and resizes an image in the background and binds it to the
+         * QuickContactBadge in each item layout of the ListView. ImageLoader implements memory
+         * caching for each image, which substantially improves refreshes of the ListView as the
+         * user scrolls through it.
+         *
+         * To learn more about downloading images asynchronously and caching the results, read the
+         * Android training class Displaying Bitmaps Efficiently.
+         *
+         * http://developer.android.com/training/displaying-bitmaps/
+         */
+        mImageLoader = new ImageLoader(getActivity(), getListPreferredItemHeight()) {
+            @Override
+            protected Bitmap processBitmap(Object data) {
+                // This gets called in a background thread and passed the data from
+                // ImageLoader.loadImage().
+                return loadContactPhotoThumbnail((String) data, getImageSize());
+            }
+        };
+
+        // Set a placeholder loading image for the image loader
+        mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_holo_light);
+
+        // Add a cache to the image loader
+        mImageLoader.addImageCache(getActivity().getSupportFragmentManager(), 0.1f);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the list fragment layout
+        return inflater.inflate(R.layout.contact_list_fragment, container, false);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Set up ListView, assign adapter and set some listeners. The adapter was previously
+        // created in onCreate().
+        setListAdapter(mAdapter);
+        getListView().setOnItemClickListener(this);
+        getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
+                // Pause image loader to ensure smoother scrolling when flinging
+                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+                    mImageLoader.setPauseWork(true);
+                } else {
+                    mImageLoader.setPauseWork(false);
+                }
+            }
+
+            @Override
+            public void onScroll(AbsListView absListView, int i, int i1, int i2) {}
+        });
+
+        if (mIsTwoPaneLayout) {
+            // In a two-pane layout, set choice mode to single as there will be two panes
+            // when an item in the ListView is selected it should remain highlighted while
+            // the content shows in the second pane.
+            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        }
+
+        // If there's a previously selected search item from a saved state then don't bother
+        // initializing the loader as it will be restarted later when the query is populated into
+        // the action bar search view (see onQueryTextChange() in onCreateOptionsMenu()).
+        if (mPreviouslySelectedSearchItem == 0) {
+            // Initialize the loader, and create a loader identified by ContactsQuery.QUERY_ID
+            getLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            // Assign callback listener which the holding activity must implement. This is used
+            // so that when a contact item is interacted with (selected by the user) the holding
+            // activity will be notified and can take further action such as populating the contact
+            // detail pane (if in multi-pane layout) or starting a new activity with the contact
+            // details (single pane layout).
+            mOnContactSelectedListener = (OnContactsInteractionListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement OnContactsInteractionListener");
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // In the case onPause() is called during a fling the image loader is
+        // un-paused to let any remaining background work complete.
+        mImageLoader.setPauseWork(false);
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+        // Gets the Cursor object currently bound to the ListView
+        final Cursor cursor = mAdapter.getCursor();
+
+        // Moves to the Cursor row corresponding to the ListView item that was clicked
+        cursor.moveToPosition(position);
+
+        // Creates a contact lookup Uri from contact ID and lookup_key
+        final Uri uri = Contacts.getLookupUri(
+                cursor.getLong(ContactsQuery.ID),
+                cursor.getString(ContactsQuery.LOOKUP_KEY));
+
+        // Notifies the parent activity that the user selected a contact. In a two-pane layout, the
+        // parent activity loads a ContactDetailFragment that displays the details for the selected
+        // contact. In a single-pane layout, the parent activity starts a new activity that
+        // displays contact details in its own Fragment.
+        mOnContactSelectedListener.onContactSelected(uri);
+
+        // If two-pane layout sets the selected item to checked so it remains highlighted. In a
+        // single-pane layout a new activity is started so this is not needed.
+        if (mIsTwoPaneLayout) {
+            getListView().setItemChecked(position, true);
+        }
+    }
+
+    /**
+     * Called when ListView selection is cleared, for example
+     * when search mode is finished and the currently selected
+     * contact should no longer be selected.
+     */
+    private void onSelectionCleared() {
+        // Uses callback to notify activity this contains this fragment
+        mOnContactSelectedListener.onSelectionCleared();
+
+        // Clears currently checked item
+        getListView().clearChoices();
+    }
+
+    // This method uses APIs from newer OS versions than the minimum that this app supports. This
+    // annotation tells Android lint that they are properly guarded so they won't run on older OS
+    // versions and can be ignored by lint.
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+
+        // Inflate the menu items
+        inflater.inflate(R.menu.contact_list_menu, menu);
+        // Locate the search item
+        MenuItem searchItem = menu.findItem(R.id.menu_search);
+
+        // In versions prior to Android 3.0, hides the search item to prevent additional
+        // searches. In Android 3.0 and later, searching is done via a SearchView in the ActionBar.
+        // Since the search doesn't create a new Activity to do the searching, the menu item
+        // doesn't need to be turned off.
+        if (mIsSearchResultView) {
+            searchItem.setVisible(false);
+        }
+
+        // In version 3.0 and later, sets up and configures the ActionBar SearchView
+        if (Utils.hasHoneycomb()) {
+
+            // Retrieves the system search manager service
+            final SearchManager searchManager =
+                    (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
+
+            // Retrieves the SearchView from the search menu item
+            final SearchView searchView = (SearchView) searchItem.getActionView();
+
+            // Assign searchable info to SearchView
+            searchView.setSearchableInfo(
+                    searchManager.getSearchableInfo(getActivity().getComponentName()));
+
+            // Set listeners for SearchView
+            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+                @Override
+                public boolean onQueryTextSubmit(String queryText) {
+                    // Nothing needs to happen when the user submits the search string
+                    return true;
+                }
+
+                @Override
+                public boolean onQueryTextChange(String newText) {
+                    // Called when the action bar search text has changed.  Updates
+                    // the search filter, and restarts the loader to do a new query
+                    // using the new search string.
+                    String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
+
+                    // Don't do anything if the filter is empty
+                    if (mSearchTerm == null && newFilter == null) {
+                        return true;
+                    }
+
+                    // Don't do anything if the new filter is the same as the current filter
+                    if (mSearchTerm != null && mSearchTerm.equals(newFilter)) {
+                        return true;
+                    }
+
+                    // Updates current filter to new filter
+                    mSearchTerm = newFilter;
+
+                    // Restarts the loader. This triggers onCreateLoader(), which builds the
+                    // necessary content Uri from mSearchTerm.
+                    mSearchQueryChanged = true;
+                    getLoaderManager().restartLoader(
+                            ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
+                    return true;
+                }
+            });
+
+            if (Utils.hasICS()) {
+                // This listener added in ICS
+                searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+                    @Override
+                    public boolean onMenuItemActionExpand(MenuItem menuItem) {
+                        // Nothing to do when the action item is expanded
+                        return true;
+                    }
+
+                    @Override
+                    public boolean onMenuItemActionCollapse(MenuItem menuItem) {
+                        // When the user collapses the SearchView the current search string is
+                        // cleared and the loader restarted.
+                        if (!TextUtils.isEmpty(mSearchTerm)) {
+                            onSelectionCleared();
+                        }
+                        mSearchTerm = null;
+                        getLoaderManager().restartLoader(
+                                ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
+                        return true;
+                    }
+                });
+            }
+
+            if (mSearchTerm != null) {
+                // If search term is already set here then this fragment is
+                // being restored from a saved state and the search menu item
+                // needs to be expanded and populated again.
+
+                // Stores the search term (as it will be wiped out by
+                // onQueryTextChange() when the menu item is expanded).
+                final String savedSearchTerm = mSearchTerm;
+
+                // Expands the search menu item
+                if (Utils.hasICS()) {
+                    searchItem.expandActionView();
+                }
+
+                // Sets the SearchView to the previous search string
+                searchView.setQuery(savedSearchTerm, false);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (!TextUtils.isEmpty(mSearchTerm)) {
+            // Saves the current search string
+            outState.putString(SearchManager.QUERY, mSearchTerm);
+
+            // Saves the currently selected contact
+            outState.putInt(STATE_PREVIOUSLY_SELECTED_KEY, getListView().getCheckedItemPosition());
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            // Sends a request to the People app to display the create contact screen
+            case R.id.menu_add_contact:
+                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+                startActivity(intent);
+                break;
+            // For platforms earlier than Android 3.0, triggers the search activity
+            case R.id.menu_search:
+                if (!Utils.hasHoneycomb()) {
+                    getActivity().onSearchRequested();
+                }
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+        // If this is the loader for finding contacts in the Contacts Provider
+        // (the only one supported)
+        if (id == ContactsQuery.QUERY_ID) {
+            Uri contentUri;
+
+            // There are two types of searches, one which displays all contacts and
+            // one which filters contacts by a search query. If mSearchTerm is set
+            // then a search query has been entered and the latter should be used.
+
+            if (mSearchTerm == null) {
+                // Since there's no search string, use the content URI that searches the entire
+                // Contacts table
+                contentUri = ContactsQuery.CONTENT_URI;
+            } else {
+                // Since there's a search string, use the special content Uri that searches the
+                // Contacts table. The URI consists of a base Uri and the search string.
+                contentUri =
+                        Uri.withAppendedPath(ContactsQuery.FILTER_URI, Uri.encode(mSearchTerm));
+            }
+
+            // Returns a new CursorLoader for querying the Contacts table. No arguments are used
+            // for the selection clause. The search string is either encoded onto the content URI,
+            // or no contacts search string is used. The other search criteria are constants. See
+            // the ContactsQuery interface.
+            return new CursorLoader(getActivity(),
+                    contentUri,
+                    ContactsQuery.PROJECTION,
+                    ContactsQuery.SELECTION,
+                    null,
+                    ContactsQuery.SORT_ORDER);
+        }
+
+        Log.e(TAG, "onCreateLoader - incorrect ID provided (" + id + ")");
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // This swaps the new cursor into the adapter.
+        if (loader.getId() == ContactsQuery.QUERY_ID) {
+            mAdapter.swapCursor(data);
+
+            // If this is a two-pane layout and there is a search query then
+            // there is some additional work to do around default selected
+            // search item.
+            if (mIsTwoPaneLayout && !TextUtils.isEmpty(mSearchTerm) && mSearchQueryChanged) {
+                // Selects the first item in results, unless this fragment has
+                // been restored from a saved state (like orientation change)
+                // in which case it selects the previously selected search item.
+                if (data != null && data.moveToPosition(mPreviouslySelectedSearchItem)) {
+                    // Creates the content Uri for the previously selected contact by appending the
+                    // contact's ID to the Contacts table content Uri
+                    final Uri uri = Uri.withAppendedPath(
+                            Contacts.CONTENT_URI, String.valueOf(data.getLong(ContactsQuery.ID)));
+                    mOnContactSelectedListener.onContactSelected(uri);
+                    getListView().setItemChecked(mPreviouslySelectedSearchItem, true);
+                } else {
+                    // No results, clear selection.
+                    onSelectionCleared();
+                }
+                // Only restore from saved state one time. Next time fall back
+                // to selecting first item. If the fragment state is saved again
+                // then the currently selected item will once again be saved.
+                mPreviouslySelectedSearchItem = 0;
+                mSearchQueryChanged = false;
+            }
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        if (loader.getId() == ContactsQuery.QUERY_ID) {
+            // When the loader is being reset, clear the cursor from the adapter. This allows the
+            // cursor resources to be freed.
+            mAdapter.swapCursor(null);
+        }
+    }
+
+    /**
+     * Gets the preferred height for each item in the ListView, in pixels, after accounting for
+     * screen density. ImageLoader uses this value to resize thumbnail images to match the ListView
+     * item height.
+     *
+     * @return The preferred height in pixels, based on the current theme.
+     */
+    private int getListPreferredItemHeight() {
+        final TypedValue typedValue = new TypedValue();
+
+        // Resolve list item preferred height theme attribute into typedValue
+        getActivity().getTheme().resolveAttribute(
+                android.R.attr.listPreferredItemHeight, typedValue, true);
+
+        // Create a new DisplayMetrics object
+        final DisplayMetrics metrics = new android.util.DisplayMetrics();
+
+        // Populate the DisplayMetrics
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+        // Return theme value based on DisplayMetrics
+        return (int) typedValue.getDimension(metrics);
+    }
+
+    /**
+     * Decodes and scales a contact's image from a file pointed to by a Uri in the contact's data,
+     * and returns the result as a Bitmap. The column that contains the Uri varies according to the
+     * platform version.
+     *
+     * @param photoData For platforms prior to Android 3.0, provide the Contact._ID column value.
+     *                  For Android 3.0 and later, provide the Contact.PHOTO_THUMBNAIL_URI value.
+     * @param imageSize The desired target width and height of the output image in pixels.
+     * @return A Bitmap containing the contact's image, resized to fit the provided image size. If
+     * no thumbnail exists, returns null.
+     */
+    private Bitmap loadContactPhotoThumbnail(String photoData, int imageSize) {
+
+        // Ensures the Fragment is still added to an activity. As this method is called in a
+        // background thread, there's the possibility the Fragment is no longer attached and
+        // added to an activity. If so, no need to spend resources loading the contact photo.
+        if (!isAdded() || getActivity() == null) {
+            return null;
+        }
+
+        // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
+        // ContentResolver can return an AssetFileDescriptor for the file.
+        AssetFileDescriptor afd = null;
+
+        // This "try" block catches an Exception if the file descriptor returned from the Contacts
+        // Provider doesn't point to an existing file.
+        try {
+            Uri thumbUri;
+            // If Android 3.0 or later, converts the Uri passed as a string to a Uri object.
+            if (Utils.hasHoneycomb()) {
+                thumbUri = Uri.parse(photoData);
+            } else {
+                // For versions prior to Android 3.0, appends the string argument to the content
+                // Uri for the Contacts table.
+                final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, photoData);
+
+                // Appends the content Uri for the Contacts.Photo table to the previously
+                // constructed contact Uri to yield a content URI for the thumbnail image
+                thumbUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
+            }
+            // Retrieves a file descriptor from the Contacts Provider. To learn more about this
+            // feature, read the reference documentation for
+            // ContentResolver#openAssetFileDescriptor.
+            afd = getActivity().getContentResolver().openAssetFileDescriptor(thumbUri, "r");
+
+            // Gets a FileDescriptor from the AssetFileDescriptor. A BitmapFactory object can
+            // decode the contents of a file pointed to by a FileDescriptor into a Bitmap.
+            FileDescriptor fileDescriptor = afd.getFileDescriptor();
+
+            if (fileDescriptor != null) {
+                // Decodes a Bitmap from the image pointed to by the FileDescriptor, and scales it
+                // to the specified width and height
+                return ImageLoader.decodeSampledBitmapFromDescriptor(
+                        fileDescriptor, imageSize, imageSize);
+            }
+        } catch (FileNotFoundException e) {
+            // If the file pointed to by the thumbnail URI doesn't exist, or the file can't be
+            // opened in "read" mode, ContentResolver.openAssetFileDescriptor throws a
+            // FileNotFoundException.
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "Contact photo thumbnail not found for contact " + photoData
+                        + ": " + e.toString());
+            }
+        } finally {
+            // If an AssetFileDescriptor was returned, try to close it
+            if (afd != null) {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    // Closing a file descriptor might cause an IOException if the file is
+                    // already closed. Nothing extra is needed to handle this.
+                }
+            }
+        }
+
+        // If the decoding failed, returns null
+        return null;
+    }
+
+    /**
+     * This is a subclass of CursorAdapter that supports binding Cursor columns to a view layout.
+     * If those items are part of search results, the search string is marked by highlighting the
+     * query text. An {@link AlphabetIndexer} is used to allow quicker navigation up and down the
+     * ListView.
+     */
+    private class ContactsAdapter extends CursorAdapter implements SectionIndexer {
+        private LayoutInflater mInflater; // Stores the layout inflater
+        private AlphabetIndexer mAlphabetIndexer; // Stores the AlphabetIndexer instance
+        private TextAppearanceSpan highlightTextSpan; // Stores the highlight text appearance style
+
+        /**
+         * Instantiates a new Contacts Adapter.
+         * @param context A context that has access to the app's layout.
+         */
+        public ContactsAdapter(Context context) {
+            super(context, null, 0);
+
+            // Stores inflater for use later
+            mInflater = LayoutInflater.from(context);
+
+            // Loads a string containing the English alphabet. To fully localize the app, provide a
+            // strings.xml file in res/values-<x> directories, where <x> is a locale. In the file,
+            // define a string with android:name="alphabet" and contents set to all of the
+            // alphabetic characters in the language in their proper sort order, in upper case if
+            // applicable.
+            final String alphabet = context.getString(R.string.alphabet);
+
+            // Instantiates a new AlphabetIndexer bound to the column used to sort contact names.
+            // The cursor is left null, because it has not yet been retrieved.
+            mAlphabetIndexer = new AlphabetIndexer(null, ContactsQuery.SORT_KEY, alphabet);
+
+            // Defines a span for highlighting the part of a display name that matches the search
+            // string
+            highlightTextSpan = new TextAppearanceSpan(getActivity(), R.style.searchTextHiglight);
+        }
+
+        /**
+         * Identifies the start of the search string in the display name column of a Cursor row.
+         * E.g. If displayName was "Adam" and search query (mSearchTerm) was "da" this would
+         * return 1.
+         *
+         * @param displayName The contact display name.
+         * @return The starting position of the search string in the display name, 0-based. The
+         * method returns -1 if the string is not found in the display name, or if the search
+         * string is empty or null.
+         */
+        private int indexOfSearchQuery(String displayName) {
+            if (!TextUtils.isEmpty(mSearchTerm)) {
+                return displayName.toLowerCase(Locale.getDefault()).indexOf(
+                        mSearchTerm.toLowerCase(Locale.getDefault()));
+            }
+            return -1;
+        }
+
+        /**
+         * Overrides newView() to inflate the list item views.
+         */
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
+            // Inflates the list item layout.
+            final View itemLayout =
+                    mInflater.inflate(R.layout.contact_list_item, viewGroup, false);
+
+            // Creates a new ViewHolder in which to store handles to each view resource. This
+            // allows bindView() to retrieve stored references instead of calling findViewById for
+            // each instance of the layout.
+            final ViewHolder holder = new ViewHolder();
+            holder.text1 = (TextView) itemLayout.findViewById(android.R.id.text1);
+            holder.text2 = (TextView) itemLayout.findViewById(android.R.id.text2);
+            holder.icon = (QuickContactBadge) itemLayout.findViewById(android.R.id.icon);
+
+            // Stores the resourceHolder instance in itemLayout. This makes resourceHolder
+            // available to bindView and other methods that receive a handle to the item view.
+            itemLayout.setTag(holder);
+
+            // Returns the item layout view
+            return itemLayout;
+        }
+
+        /**
+         * Binds data from the Cursor to the provided view.
+         */
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Gets handles to individual view resources
+            final ViewHolder holder = (ViewHolder) view.getTag();
+
+            // For Android 3.0 and later, gets the thumbnail image Uri from the current Cursor row.
+            // For platforms earlier than 3.0, this isn't necessary, because the thumbnail is
+            // generated from the other fields in the row.
+            final String photoUri = cursor.getString(ContactsQuery.PHOTO_THUMBNAIL_DATA);
+
+            final String displayName = cursor.getString(ContactsQuery.DISPLAY_NAME);
+
+            final int startIndex = indexOfSearchQuery(displayName);
+
+            if (startIndex == -1) {
+                // If the user didn't do a search, or the search string didn't match a display
+                // name, show the display name without highlighting
+                holder.text1.setText(displayName);
+
+                if (TextUtils.isEmpty(mSearchTerm)) {
+                    // If the search search is empty, hide the second line of text
+                    holder.text2.setVisibility(View.GONE);
+                } else {
+                    // Shows a second line of text that indicates the search string matched
+                    // something other than the display name
+                    holder.text2.setVisibility(View.VISIBLE);
+                }
+            } else {
+                // If the search string matched the display name, applies a SpannableString to
+                // highlight the search string with the displayed display name
+
+                // Wraps the display name in the SpannableString
+                final SpannableString highlightedName = new SpannableString(displayName);
+
+                // Sets the span to start at the starting point of the match and end at "length"
+                // characters beyond the starting point
+                highlightedName.setSpan(highlightTextSpan, startIndex,
+                        startIndex + mSearchTerm.length(), 0);
+
+                // Binds the SpannableString to the display name View object
+                holder.text1.setText(highlightedName);
+
+                // Since the search string matched the name, this hides the secondary message
+                holder.text2.setVisibility(View.GONE);
+            }
+
+            // Processes the QuickContactBadge. A QuickContactBadge first appears as a contact's
+            // thumbnail image with styling that indicates it can be touched for additional
+            // information. When the user clicks the image, the badge expands into a dialog box
+            // containing the contact's details and icons for the built-in apps that can handle
+            // each detail type.
+
+            // Generates the contact lookup Uri
+            final Uri contactUri = Contacts.getLookupUri(
+                    cursor.getLong(ContactsQuery.ID),
+                    cursor.getString(ContactsQuery.LOOKUP_KEY));
+
+            // Binds the contact's lookup Uri to the QuickContactBadge
+            holder.icon.assignContactUri(contactUri);
+
+            // Loads the thumbnail image pointed to by photoUri into the QuickContactBadge in a
+            // background worker thread
+            mImageLoader.loadImage(photoUri, holder.icon);
+        }
+
+        /**
+         * Overrides swapCursor to move the new Cursor into the AlphabetIndex as well as the
+         * CursorAdapter.
+         */
+        @Override
+        public Cursor swapCursor(Cursor newCursor) {
+            // Update the AlphabetIndexer with new cursor as well
+            mAlphabetIndexer.setCursor(newCursor);
+            return super.swapCursor(newCursor);
+        }
+
+        /**
+         * An override of getCount that simplifies accessing the Cursor. If the Cursor is null,
+         * getCount returns zero. As a result, no test for Cursor == null is needed.
+         */
+        @Override
+        public int getCount() {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return super.getCount();
+        }
+
+        /**
+         * Defines the SectionIndexer.getSections() interface.
+         */
+        @Override
+        public Object[] getSections() {
+            return mAlphabetIndexer.getSections();
+        }
+
+        /**
+         * Defines the SectionIndexer.getPositionForSection() interface.
+         */
+        @Override
+        public int getPositionForSection(int i) {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return mAlphabetIndexer.getPositionForSection(i);
+        }
+
+        /**
+         * Defines the SectionIndexer.getSectionForPosition() interface.
+         */
+        @Override
+        public int getSectionForPosition(int i) {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return mAlphabetIndexer.getSectionForPosition(i);
+        }
+
+        /**
+         * A class that defines fields for each resource ID in the list item layout. This allows
+         * ContactsAdapter.newView() to store the IDs once, when it inflates the layout, instead of
+         * calling findViewById in each iteration of bindView.
+         */
+        private class ViewHolder {
+            TextView text1;
+            TextView text2;
+            QuickContactBadge icon;
+        }
+    }
+
+    /**
+     * This interface must be implemented by any activity that loads this fragment. When an
+     * interaction occurs, such as touching an item from the ListView, these callbacks will
+     * be invoked to communicate the event back to the activity.
+     */
+    public interface OnContactsInteractionListener {
+        /**
+         * Called when a contact is selected from the ListView.
+         * @param contactUri The contact Uri.
+         */
+        public void onContactSelected(Uri contactUri);
+
+        /**
+         * Called when the ListView selection is cleared like when
+         * a contact search is taking place or is finishing.
+         */
+        public void onSelectionCleared();
+    }
+
+    /**
+     * This interface defines constants for the Cursor and CursorLoader, based on constants defined
+     * in the {@link android.provider.ContactsContract.Contacts} class.
+     */
+    public interface ContactsQuery {
+
+        // An identifier for the loader
+        final static int QUERY_ID = 1;
+
+        // A content URI for the Contacts table
+        final static Uri CONTENT_URI = Contacts.CONTENT_URI;
+
+        // The search/filter query Uri
+        final static Uri FILTER_URI = Contacts.CONTENT_FILTER_URI;
+
+        // The selection clause for the CursorLoader query. The search criteria defined here
+        // restrict results to contacts that have a display name and are linked to visible groups.
+        // Notice that the search on the string provided by the user is implemented by appending
+        // the search string to CONTENT_FILTER_URI.
+        @SuppressLint("InlinedApi")
+        final static String SELECTION =
+                (Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
+                "<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
+
+        // The desired sort order for the returned Cursor. In Android 3.0 and later, the primary
+        // sort key allows for localization. In earlier versions. use the display name as the sort
+        // key.
+        @SuppressLint("InlinedApi")
+        final static String SORT_ORDER =
+                Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;
+
+        // The projection for the CursorLoader query. This is a list of columns that the Contacts
+        // Provider should return in the Cursor.
+        @SuppressLint("InlinedApi")
+        final static String[] PROJECTION = {
+
+                // The contact's row id
+                Contacts._ID,
+
+                // A pointer to the contact that is guaranteed to be more permanent than _ID. Given
+                // a contact's current _ID value and LOOKUP_KEY, the Contacts Provider can generate
+                // a "permanent" contact URI.
+                Contacts.LOOKUP_KEY,
+
+                // In platform version 3.0 and later, the Contacts table contains
+                // DISPLAY_NAME_PRIMARY, which either contains the contact's displayable name or
+                // some other useful identifier such as an email address. This column isn't
+                // available in earlier versions of Android, so you must use Contacts.DISPLAY_NAME
+                // instead.
+                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
+
+                // In Android 3.0 and later, the thumbnail image is pointed to by
+                // PHOTO_THUMBNAIL_URI. In earlier versions, there is no direct pointer; instead,
+                // you generate the pointer from the contact's ID value and constants defined in
+                // android.provider.ContactsContract.Contacts.
+                Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID,
+
+                // The sort order column for the returned Cursor, used by the AlphabetIndexer
+                SORT_ORDER,
+        };
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int LOOKUP_KEY = 1;
+        final static int DISPLAY_NAME = 2;
+        final static int PHOTO_THUMBNAIL_DATA = 3;
+        final static int SORT_KEY = 4;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java
new file mode 100644
index 0000000..7e86018
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java
@@ -0,0 +1,227 @@
+/*
+ * 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.example.android.contactslist.util;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.util.LruCache;
+import android.util.Log;
+
+import com.example.android.contactslist.BuildConfig;
+
+/**
+ * This class holds our bitmap caches (memory and disk).
+ */
+public class ImageCache {
+    private static final String TAG = "ImageCache";
+    private LruCache<String, Bitmap> mMemoryCache;
+
+    /**
+     * Creating a new ImageCache object using the specified parameters.
+     *
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    private ImageCache(float memCacheSizePercent) {
+        init(memCacheSizePercent);
+    }
+
+    /**
+     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
+     * one is created using the supplied params and saved to a {@link RetainFragment}.
+     *
+     * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     * @return An existing retained ImageCache object or a new one if one did not exist
+     */
+    public static ImageCache getInstance(
+            FragmentManager fragmentManager, float memCacheSizePercent) {
+
+        // Search for, or create an instance of the non-UI RetainFragment
+        final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
+
+        // See if we already have an ImageCache stored in RetainFragment
+        ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
+
+        // No existing ImageCache, create one and store it in RetainFragment
+        if (imageCache == null) {
+            imageCache = new ImageCache(memCacheSizePercent);
+            mRetainFragment.setObject(imageCache);
+        }
+
+        return imageCache;
+    }
+
+    /**
+     * Initialize the cache.
+     *
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    private void init(float memCacheSizePercent) {
+        int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
+
+        // Set up memory cache
+        if (BuildConfig.DEBUG) {
+            Log.d(TAG, "Memory cache created (size = " + memCacheSize + ")");
+        }
+        mMemoryCache = new LruCache<String, Bitmap>(memCacheSize) {
+            /**
+             * Measure item size in kilobytes rather than units which is more practical
+             * for a bitmap cache
+             */
+            @Override
+            protected int sizeOf(String key, Bitmap bitmap) {
+                final int bitmapSize = getBitmapSize(bitmap) / 1024;
+                return bitmapSize == 0 ? 1 : bitmapSize;
+            }
+        };
+    }
+
+    /**
+     * Adds a bitmap to both memory and disk cache.
+     * @param data Unique identifier for the bitmap to store
+     * @param bitmap The bitmap to store
+     */
+    public void addBitmapToCache(String data, Bitmap bitmap) {
+        if (data == null || bitmap == null) {
+            return;
+        }
+
+        // Add to memory cache
+        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
+            mMemoryCache.put(data, bitmap);
+        }
+    }
+
+    /**
+     * Get from memory cache.
+     *
+     * @param data Unique identifier for which item to get
+     * @return The bitmap if found in cache, null otherwise
+     */
+    public Bitmap getBitmapFromMemCache(String data) {
+        if (mMemoryCache != null) {
+            final Bitmap memBitmap = mMemoryCache.get(data);
+            if (memBitmap != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "Memory cache hit");
+                }
+                return memBitmap;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the size in bytes of a bitmap.
+     *
+     * @param bitmap The bitmap to calculate the size of.
+     * @return size of bitmap in bytes.
+     */
+    @TargetApi(12)
+    public static int getBitmapSize(Bitmap bitmap) {
+        if (Utils.hasHoneycombMR1()) {
+            return bitmap.getByteCount();
+        }
+        // Pre HC-MR1
+        return bitmap.getRowBytes() * bitmap.getHeight();
+    }
+
+    /**
+     * Calculates the memory cache size based on a percentage of the max available VM memory.
+     * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
+     * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
+     * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
+     * to construct a LruCache which takes an int in its constructor.
+     *
+     * This value should be chosen carefully based on a number of factors
+     * Refer to the corresponding Android Training class for more discussion:
+     * http://developer.android.com/training/displaying-bitmaps/
+     *
+     * @param percent Percent of available app memory to use to size memory cache.
+     */
+    public static int calculateMemCacheSize(float percent) {
+        if (percent < 0.05f || percent > 0.8f) {
+            throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+                    + "between 0.05 and 0.8 (inclusive)");
+        }
+        return Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
+    }
+
+    /**
+     * Locate an existing instance of this Fragment or if not found, create and
+     * add it using FragmentManager.
+     *
+     * @param fm The FragmentManager manager to use.
+     * @return The existing instance of the Fragment or the new instance if just
+     *         created.
+     */
+    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
+        // Check to see if we have retained the worker fragment.
+        RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
+
+        // If not retained (or first time running), we need to create and add it.
+        if (mRetainFragment == null) {
+            mRetainFragment = new RetainFragment();
+            fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
+        }
+
+        return mRetainFragment;
+    }
+
+    /**
+     * A simple non-UI Fragment that stores a single Object and is retained over configuration
+     * changes. It will be used to retain the ImageCache object.
+     */
+    public static class RetainFragment extends Fragment {
+        private Object mObject;
+
+        /**
+         * Empty constructor as per the Fragment documentation
+         */
+        public RetainFragment() {}
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            // Make sure this Fragment is retained over a configuration change
+            setRetainInstance(true);
+        }
+
+        /**
+         * Store a single object in this Fragment.
+         *
+         * @param object The object to store
+         */
+        public void setObject(Object object) {
+            mObject = object;
+        }
+
+        /**
+         * Get the stored object.
+         *
+         * @return The stored object
+         */
+        public Object getObject() {
+            return mObject;
+        }
+    }
+
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java
new file mode 100644
index 0000000..7d915bb
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java
@@ -0,0 +1,422 @@
+/*
+ * 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.example.android.contactslist.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.AsyncTask;
+import android.support.v4.app.FragmentManager;
+import android.util.Log;
+import android.widget.ImageView;
+
+import com.example.android.contactslist.BuildConfig;
+
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+
+/**
+ * This class wraps up completing some arbitrary long running work when loading a bitmap to an
+ * ImageView. It handles things like using a memory and disk cache, running the work in a background
+ * thread and setting a placeholder image.
+ */
+public abstract class ImageLoader {
+    private static final String TAG = "ImageLoader";
+    private static final int FADE_IN_TIME = 200;
+
+    private ImageCache mImageCache;
+    private Bitmap mLoadingBitmap;
+    private boolean mFadeInBitmap = true;
+    private boolean mPauseWork = false;
+    private final Object mPauseWorkLock = new Object();
+    private int mImageSize;
+    private Resources mResources;
+
+    protected ImageLoader(Context context, int imageSize) {
+        mResources = context.getResources();
+        mImageSize = imageSize;
+    }
+
+    public int getImageSize() {
+        return mImageSize;
+    }
+
+    /**
+     * Load an image specified by the data parameter into an ImageView (override
+     * {@link ImageLoader#processBitmap(Object)} to define the processing logic). If the image is
+     * found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} will be
+     * created to asynchronously load the bitmap.
+     *
+     * @param data The URL of the image to download.
+     * @param imageView The ImageView to bind the downloaded image to.
+     */
+    public void loadImage(Object data, ImageView imageView) {
+        if (data == null) {
+            imageView.setImageBitmap(mLoadingBitmap);
+            return;
+        }
+
+        Bitmap bitmap = null;
+
+        if (mImageCache != null) {
+            bitmap = mImageCache.getBitmapFromMemCache(String.valueOf(data));
+        }
+
+        if (bitmap != null) {
+            // Bitmap found in memory cache
+            imageView.setImageBitmap(bitmap);
+        } else if (cancelPotentialWork(data, imageView)) {
+            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+            final AsyncDrawable asyncDrawable =
+                    new AsyncDrawable(mResources, mLoadingBitmap, task);
+            imageView.setImageDrawable(asyncDrawable);
+            task.execute(data);
+        }
+    }
+
+    /**
+     * Set placeholder bitmap that shows when the the background thread is running.
+     *
+     * @param resId Resource ID of loading image.
+     */
+    public void setLoadingImage(int resId) {
+        mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
+    }
+
+    /**
+     * Adds an {@link ImageCache} to this image loader.
+     *
+     * @param fragmentManager A FragmentManager to use to retain the cache over configuration
+     *                        changes such as an orientation change.
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    public void addImageCache(FragmentManager fragmentManager, float memCacheSizePercent) {
+        mImageCache = ImageCache.getInstance(fragmentManager, memCacheSizePercent);
+    }
+
+    /**
+     * If set to true, the image will fade-in once it has been loaded by the background thread.
+     */
+    public void setImageFadeIn(boolean fadeIn) {
+        mFadeInBitmap = fadeIn;
+    }
+
+    /**
+     * Subclasses should override this to define any processing or work that must happen to produce
+     * the final bitmap. This will be executed in a background thread and be long running. For
+     * example, you could resize a large bitmap here, or pull down an image from the network.
+     *
+     * @param data The data to identify which image to process, as provided by
+     *            {@link ImageLoader#loadImage(Object, ImageView)}
+     * @return The processed bitmap
+     */
+    protected abstract Bitmap processBitmap(Object data);
+
+    /**
+     * Cancels any pending work attached to the provided ImageView.
+     */
+    public static void cancelWork(ImageView imageView) {
+        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+        if (bitmapWorkerTask != null) {
+            bitmapWorkerTask.cancel(true);
+            if (BuildConfig.DEBUG) {
+                final Object bitmapData = bitmapWorkerTask.data;
+                Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the current work has been canceled or if there was no work in
+     * progress on this image view.
+     * Returns false if the work in progress deals with the same data. The work is not
+     * stopped in that case.
+     */
+    public static boolean cancelPotentialWork(Object data, ImageView imageView) {
+        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+        if (bitmapWorkerTask != null) {
+            final Object bitmapData = bitmapWorkerTask.data;
+            if (bitmapData == null || !bitmapData.equals(data)) {
+                bitmapWorkerTask.cancel(true);
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
+                }
+            } else {
+                // The same work is already in progress.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @param imageView Any imageView
+     * @return Retrieve the currently active work task (if any) associated with this imageView.
+     * null if there is no such task.
+     */
+    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+        if (imageView != null) {
+            final Drawable drawable = imageView.getDrawable();
+            if (drawable instanceof AsyncDrawable) {
+                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+                return asyncDrawable.getBitmapWorkerTask();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * The actual AsyncTask that will asynchronously process the image.
+     */
+    private class BitmapWorkerTask extends AsyncTask<Object, Void, Bitmap> {
+        private Object data;
+        private final WeakReference<ImageView> imageViewReference;
+
+        public BitmapWorkerTask(ImageView imageView) {
+            imageViewReference = new WeakReference<ImageView>(imageView);
+        }
+
+        /**
+         * Background processing.
+         */
+        @Override
+        protected Bitmap doInBackground(Object... params) {
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "doInBackground - starting work");
+            }
+
+            data = params[0];
+            final String dataString = String.valueOf(data);
+            Bitmap bitmap = null;
+
+            // Wait here if work is paused and the task is not cancelled
+            synchronized (mPauseWorkLock) {
+                while (mPauseWork && !isCancelled()) {
+                    try {
+                        mPauseWorkLock.wait();
+                    } catch (InterruptedException e) {}
+                }
+            }
+
+            // If the task has not been cancelled by another thread and the ImageView that was
+            // originally bound to this task is still bound back to this task and our "exit early"
+            // flag is not set, then call the main process method (as implemented by a subclass)
+            if (!isCancelled() && getAttachedImageView() != null) {
+                bitmap = processBitmap(params[0]);
+            }
+
+            // If the bitmap was processed and the image cache is available, then add the processed
+            // bitmap to the cache for future use. Note we don't check if the task was cancelled
+            // here, if it was, and the thread is still running, we may as well add the processed
+            // bitmap to our cache as it might be used again in the future
+            if (bitmap != null && mImageCache != null) {
+                mImageCache.addBitmapToCache(dataString, bitmap);
+            }
+
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "doInBackground - finished work");
+            }
+
+            return bitmap;
+        }
+
+        /**
+         * Once the image is processed, associates it to the imageView
+         */
+        @Override
+        protected void onPostExecute(Bitmap bitmap) {
+            // if cancel was called on this task or the "exit early" flag is set then we're done
+            if (isCancelled()) {
+                bitmap = null;
+            }
+
+            final ImageView imageView = getAttachedImageView();
+            if (bitmap != null && imageView != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "onPostExecute - setting bitmap");
+                }
+                setImageBitmap(imageView, bitmap);
+            }
+        }
+
+        @Override
+        protected void onCancelled(Bitmap bitmap) {
+            super.onCancelled(bitmap);
+            synchronized (mPauseWorkLock) {
+                mPauseWorkLock.notifyAll();
+            }
+        }
+
+        /**
+         * Returns the ImageView associated with this task as long as the ImageView's task still
+         * points to this task as well. Returns null otherwise.
+         */
+        private ImageView getAttachedImageView() {
+            final ImageView imageView = imageViewReference.get();
+            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+            if (this == bitmapWorkerTask) {
+                return imageView;
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * A custom Drawable that will be attached to the imageView while the work is in progress.
+     * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
+     * required, and makes sure that only the last started worker process can bind its result,
+     * independently of the finish order.
+     */
+    private static class AsyncDrawable extends BitmapDrawable {
+        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+        public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+            super(res, bitmap);
+            bitmapWorkerTaskReference =
+                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
+        }
+
+        public BitmapWorkerTask getBitmapWorkerTask() {
+            return bitmapWorkerTaskReference.get();
+        }
+    }
+
+    /**
+     * Called when the processing is complete and the final bitmap should be set on the ImageView.
+     *
+     * @param imageView The ImageView to set the bitmap to.
+     * @param bitmap The new bitmap to set.
+     */
+    private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
+        if (mFadeInBitmap) {
+            // Transition drawable to fade from loading bitmap to final bitmap
+            final TransitionDrawable td =
+                    new TransitionDrawable(new Drawable[] {
+                            new ColorDrawable(android.R.color.transparent),
+                            new BitmapDrawable(mResources, bitmap)
+                    });
+            imageView.setBackgroundDrawable(imageView.getDrawable());
+            imageView.setImageDrawable(td);
+            td.startTransition(FADE_IN_TIME);
+        } else {
+            imageView.setImageBitmap(bitmap);
+        }
+    }
+
+    /**
+     * Pause any ongoing background work. This can be used as a temporary
+     * measure to improve performance. For example background work could
+     * be paused when a ListView or GridView is being scrolled using a
+     * {@link android.widget.AbsListView.OnScrollListener} to keep
+     * scrolling smooth.
+     * <p>
+     * If work is paused, be sure setPauseWork(false) is called again
+     * before your fragment or activity is destroyed (for example during
+     * {@link android.app.Activity#onPause()}), or there is a risk the
+     * background thread will never finish.
+     */
+    public void setPauseWork(boolean pauseWork) {
+        synchronized (mPauseWorkLock) {
+            mPauseWork = pauseWork;
+            if (!mPauseWork) {
+                mPauseWorkLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Decode and sample down a bitmap from a file input stream to the requested width and height.
+     *
+     * @param fileDescriptor The file descriptor to read from
+     * @param reqWidth The requested width of the resulting bitmap
+     * @param reqHeight The requested height of the resulting bitmap
+     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
+     *         that are equal to or greater than the requested width and height
+     */
+    public static Bitmap decodeSampledBitmapFromDescriptor(
+            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
+
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+    }
+
+    /**
+     * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
+     * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
+     * the closest inSampleSize that will result in the final decoded bitmap having a width and
+     * height equal to or larger than the requested width and height. This implementation does not
+     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
+     * results in a larger bitmap which isn't as useful for caching purposes.
+     *
+     * @param options An options object with out* params already populated (run through a decode*
+     *            method with inJustDecodeBounds==true
+     * @param reqWidth The requested width of the resulting bitmap
+     * @param reqHeight The requested height of the resulting bitmap
+     * @return The value to be used for inSampleSize
+     */
+    public static int calculateInSampleSize(BitmapFactory.Options options,
+            int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            // Calculate ratios of height and width to requested height and width
+            final int heightRatio = Math.round((float) height / (float) reqHeight);
+            final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
+            // with both dimensions larger than or equal to the requested height and width.
+            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+
+            // This offers some additional logic in case the image has a strange
+            // aspect ratio. For example, a panorama may have a much larger
+            // width than height. In these cases the total pixels might still
+            // end up being too large to fit comfortably in memory, so we should
+            // be more aggressive with sample down the image (=larger inSampleSize).
+
+            final float totalPixels = width * height;
+
+            // Anything more than 2x the requested pixels we'll sample down further
+            final float totalReqPixelsCap = reqWidth * reqHeight * 2;
+
+            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
+                inSampleSize++;
+            }
+        }
+        return inSampleSize;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java
new file mode 100644
index 0000000..9c0cc44
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java
@@ -0,0 +1,103 @@
+/*
+ * 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.example.android.contactslist.util;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.StrictMode;
+
+import com.example.android.contactslist.ui.ContactDetailActivity;
+import com.example.android.contactslist.ui.ContactsListActivity;
+
+/**
+ * This class contains static utility methods.
+ */
+public class Utils {
+
+    // Prevents instantiation.
+    private Utils() {}
+
+    /**
+     * Enables strict mode. This should only be called when debugging the application and is useful
+     * for finding some potential bugs or best practice violations.
+     */
+    @TargetApi(11)
+    public static void enableStrictMode() {
+        // Strict mode is only available on gingerbread or later
+        if (Utils.hasGingerbread()) {
+
+            // Enable all thread strict mode policies
+            StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
+                    new StrictMode.ThreadPolicy.Builder()
+                            .detectAll()
+                            .penaltyLog();
+
+            // Enable all VM strict mode policies
+            StrictMode.VmPolicy.Builder vmPolicyBuilder =
+                    new StrictMode.VmPolicy.Builder()
+                            .detectAll()
+                            .penaltyLog();
+
+            // Honeycomb introduced some additional strict mode features
+            if (Utils.hasHoneycomb()) {
+                // Flash screen when thread policy is violated
+                threadPolicyBuilder.penaltyFlashScreen();
+                // For each activity class, set an instance limit of 1. Any more instances and
+                // there could be a memory leak.
+                vmPolicyBuilder
+                        .setClassInstanceLimit(ContactsListActivity.class, 1)
+                        .setClassInstanceLimit(ContactDetailActivity.class, 1);
+            }
+
+            // Use builders to enable strict mode policies
+            StrictMode.setThreadPolicy(threadPolicyBuilder.build());
+            StrictMode.setVmPolicy(vmPolicyBuilder.build());
+        }
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Gingerbread or
+     * later.
+     */
+    public static boolean hasGingerbread() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Honeycomb or
+     * later.
+     */
+    public static boolean hasHoneycomb() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Honeycomb MR1 or
+     * later.
+     */
+    public static boolean hasHoneycombMR1() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is ICS or
+     * later.
+     */
+    public static boolean hasICS() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+    }
+}
diff --git a/samples/training/bitmapfun/AndroidManifest.xml b/samples/training/bitmapfun/AndroidManifest.xml
index cabb442..bc1c180 100644
--- a/samples/training/bitmapfun/AndroidManifest.xml
+++ b/samples/training/bitmapfun/AndroidManifest.xml
@@ -22,7 +22,7 @@
 
     <uses-sdk
         android:minSdkVersion="7"
-        android:targetSdkVersion="16" />
+        android:targetSdkVersion="17" />
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/samples/training/bitmapfun/libs/android-support-v4.jar b/samples/training/bitmapfun/libs/android-support-v4.jar
index feaf44f..6080877 100644
--- a/samples/training/bitmapfun/libs/android-support-v4.jar
+++ b/samples/training/bitmapfun/libs/android-support-v4.jar
Binary files differ
diff --git a/samples/training/bitmapfun/project.properties b/samples/training/bitmapfun/project.properties
index 9b84a6b..a3ee5ab 100644
--- a/samples/training/bitmapfun/project.properties
+++ b/samples/training/bitmapfun/project.properties
@@ -11,4 +11,4 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-16
+target=android-17
diff --git a/samples/training/bitmapfun/res/layout/image_detail_fragment.xml b/samples/training/bitmapfun/res/layout/image_detail_fragment.xml
index ff616da..6940357 100644
--- a/samples/training/bitmapfun/res/layout/image_detail_fragment.xml
+++ b/samples/training/bitmapfun/res/layout/image_detail_fragment.xml
@@ -25,7 +25,7 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center" />
 
-    <ImageView
+    <com.example.android.bitmapfun.ui.RecyclingImageView
         android:id="@+id/imageView"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/provider/Images.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/provider/Images.java
index 809d73b..b555784 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/provider/Images.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/provider/Images.java
@@ -24,92 +24,222 @@
     /**
      * This are PicasaWeb URLs and could potentially change. Ideally the PicasaWeb API should be
      * used to fetch the URLs.
+     *
+     * Credit to Romain Guy for the photos:
+     * http://www.curious-creature.org/
+     * https://plus.google.com/109538161516040592207/about
+     * http://www.flickr.com/photos/romainguy
      */
     public final static String[] imageUrls = new String[] {
-        "http://lh6.googleusercontent.com/-jZgveEqb6pg/T3R4kXScycI/AAAAAAAAAE0/xQ7CvpfXDzc/s1024/sample_image_01.jpg",
-        "http://lh4.googleusercontent.com/-K2FMuOozxU0/T3R4lRAiBTI/AAAAAAAAAE8/a3Eh9JvnnzI/s1024/sample_image_02.jpg",
-        "http://lh5.googleusercontent.com/-SCS5C646rxM/T3R4l7QB6xI/AAAAAAAAAFE/xLcuVv3CUyA/s1024/sample_image_03.jpg",
-        "http://lh6.googleusercontent.com/-f0NJR6-_Thg/T3R4mNex2wI/AAAAAAAAAFI/45oug4VE8MI/s1024/sample_image_04.jpg",
-        "http://lh3.googleusercontent.com/-n-xcJmiI0pg/T3R4mkSchHI/AAAAAAAAAFU/EoiNNb7kk3A/s1024/sample_image_05.jpg",
-        "http://lh3.googleusercontent.com/-X43vAudm7f4/T3R4nGSChJI/AAAAAAAAAFk/3bna6D-2EE8/s1024/sample_image_06.jpg",
-        "http://lh5.googleusercontent.com/-MpZneqIyjXU/T3R4nuGO1aI/AAAAAAAAAFg/r09OPjLx1ZY/s1024/sample_image_07.jpg",
-        "http://lh6.googleusercontent.com/-ql3YNfdClJo/T3XvW9apmFI/AAAAAAAAAL4/_6HFDzbahc4/s1024/sample_image_08.jpg",
-        "http://lh5.googleusercontent.com/-Pxa7eqF4cyc/T3R4oasvPEI/AAAAAAAAAF0/-uYDH92h8LA/s1024/sample_image_09.jpg",
-        "http://lh4.googleusercontent.com/-Li-rjhFEuaI/T3R4o-VUl4I/AAAAAAAAAF8/5E5XdMnP1oE/s1024/sample_image_10.jpg",
-        "http://lh5.googleusercontent.com/-_HU4fImgFhA/T3R4pPVIwWI/AAAAAAAAAGA/0RfK_Vkgth4/s1024/sample_image_11.jpg",
-        "http://lh6.googleusercontent.com/-0gnNrVjwa0Y/T3R4peGYJwI/AAAAAAAAAGU/uX_9wvRPM9I/s1024/sample_image_12.jpg",
-        "http://lh3.googleusercontent.com/-HBxuzALS_Zs/T3R4qERykaI/AAAAAAAAAGQ/_qQ16FaZ1q0/s1024/sample_image_13.jpg",
-        "http://lh4.googleusercontent.com/-cKojDrARNjQ/T3R4qfWSGPI/AAAAAAAAAGY/MR5dnbNaPyY/s1024/sample_image_14.jpg",
-        "http://lh3.googleusercontent.com/-WujkdYfcyZ8/T3R4qrIMGUI/AAAAAAAAAGk/277LIdgvnjg/s1024/sample_image_15.jpg",
-        "http://lh6.googleusercontent.com/-FMHR7Vy3PgI/T3R4rOXlEKI/AAAAAAAAAGs/VeXrDNDBkaw/s1024/sample_image_16.jpg",
-        "http://lh4.googleusercontent.com/-mrR0AJyNTH0/T3R4rZs6CuI/AAAAAAAAAG0/UE1wQqCOqLA/s1024/sample_image_17.jpg",
-        "http://lh6.googleusercontent.com/-z77w0eh3cow/T3R4rnLn05I/AAAAAAAAAG4/BaerfWoNucU/s1024/sample_image_18.jpg",
-        "http://lh5.googleusercontent.com/-aWVwh1OU5Bk/T3R4sAWw0yI/AAAAAAAAAHE/4_KAvJttFwA/s1024/sample_image_19.jpg",
-        "http://lh6.googleusercontent.com/-q-js52DMnWQ/T3R4tZhY2sI/AAAAAAAAAHM/A8kjp2Ivdqg/s1024/sample_image_20.jpg",
-        "http://lh5.googleusercontent.com/-_jIzvvzXKn4/T3R4t7xpdVI/AAAAAAAAAHU/7QC6eZ10jgs/s1024/sample_image_21.jpg",
-        "http://lh3.googleusercontent.com/-lnGi4IMLpwU/T3R4uCMa7vI/AAAAAAAAAHc/1zgzzz6qTpk/s1024/sample_image_22.jpg",
-        "http://lh5.googleusercontent.com/-fFCzKjFPsPc/T3R4u0SZPFI/AAAAAAAAAHk/sbgjzrktOK0/s1024/sample_image_23.jpg",
-        "http://lh4.googleusercontent.com/-8TqoW5gBE_Y/T3R4vBS3NPI/AAAAAAAAAHs/EZYvpNsaNXk/s1024/sample_image_24.jpg",
-        "http://lh6.googleusercontent.com/-gc4eQ3ySdzs/T3R4vafoA7I/AAAAAAAAAH4/yKii5P6tqDE/s1024/sample_image_25.jpg",
-        "http://lh5.googleusercontent.com/--NYOPCylU7Q/T3R4vjAiWkI/AAAAAAAAAH8/IPNx5q3ptRA/s1024/sample_image_26.jpg",
-        "http://lh6.googleusercontent.com/-9IJM8so4vCI/T3R4vwJO2yI/AAAAAAAAAIE/ljlr-cwuqZM/s1024/sample_image_27.jpg",
-        "http://lh4.googleusercontent.com/-KW6QwOHfhBs/T3R4w0RsQiI/AAAAAAAAAIM/uEFLVgHPFCk/s1024/sample_image_28.jpg",
-        "http://lh4.googleusercontent.com/-z2557Ec1ctY/T3R4x3QA2hI/AAAAAAAAAIk/9-GzPL1lTWE/s1024/sample_image_29.jpg",
-        "http://lh5.googleusercontent.com/-LaKXAn4Kr1c/T3R4yc5b4lI/AAAAAAAAAIY/fMgcOVQfmD0/s1024/sample_image_30.jpg",
-        "http://lh4.googleusercontent.com/-F9LRToJoQdo/T3R4yrLtyQI/AAAAAAAAAIg/ri9uUCWuRmo/s1024/sample_image_31.jpg",
-        "http://lh4.googleusercontent.com/-6X-xBwP-QpI/T3R4zGVboII/AAAAAAAAAIs/zYH4PjjngY0/s1024/sample_image_32.jpg",
-        "http://lh5.googleusercontent.com/-VdLRjbW4LAs/T3R4zXu3gUI/AAAAAAAAAIw/9aFp9t7mCPg/s1024/sample_image_33.jpg",
-        "http://lh6.googleusercontent.com/-gL6R17_fDJU/T3R4zpIXGjI/AAAAAAAAAI8/Q2Vjx-L9X20/s1024/sample_image_34.jpg",
-        "http://lh3.googleusercontent.com/-1fGH4YJXEzo/T3R40Y1B7KI/AAAAAAAAAJE/MnTsa77g-nk/s1024/sample_image_35.jpg",
-        "http://lh4.googleusercontent.com/-Ql0jHSrea-A/T3R403mUfFI/AAAAAAAAAJM/qzI4SkcH9tY/s1024/sample_image_36.jpg",
-        "http://lh5.googleusercontent.com/-BL5FIBR_tzI/T3R41DA0AKI/AAAAAAAAAJk/GZfeeb-SLM0/s1024/sample_image_37.jpg",
-        "http://lh4.googleusercontent.com/-wF2Vc9YDutw/T3R41fR2BCI/AAAAAAAAAJc/JdU1sHdMRAk/s1024/sample_image_38.jpg",
-        "http://lh6.googleusercontent.com/-ZWHiPehwjTI/T3R41zuaKCI/AAAAAAAAAJg/hR3QJ1v3REg/s1024/sample_image_39.jpg",
+            "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg",
+            "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s1024/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg",
+            "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s1024/Another%252520Rockaway%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s1024/Antelope%252520Butte.jpg",
+            "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s1024/Antelope%252520Hallway.jpg",
+            "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s1024/Antelope%252520Walls.jpg",
+            "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s1024/Apre%2525CC%252580s%252520la%252520Pluie.jpg",
+            "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s1024/Backlit%252520Cloud.jpg",
+            "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s1024/Bee%252520and%252520Flower.jpg",
+            "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s1024/Bonzai%252520Rock%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s1024/Caterpillar.jpg",
+            "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s1024/Chess.jpg",
+            "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s1024/Chihuly.jpg",
+            "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s1024/Closed%252520Door.jpg",
+            "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s1024/Colorado%252520River%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s1024/Colors%252520of%252520Autumn.jpg",
+            "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s1024/Countryside.jpg",
+            "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s1024/Death%252520Valley%252520-%252520Dunes.jpg",
+            "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s1024/Delicate%252520Arch.jpg",
+            "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s1024/Despair.jpg",
+            "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s1024/Eagle%252520Fall%252520Sunrise.jpg",
+            "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s1024/Electric%252520Storm.jpg",
+            "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s1024/False%252520Kiva.jpg",
+            "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s1024/Fitzgerald%252520Streaks.jpg",
+            "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s1024/Foggy%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s1024/Frantic.jpg",
+            "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s1024/Golden%252520Gate%252520Afternoon.jpg",
+            "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s1024/Golden%252520Gate%252520Fog.jpg",
+            "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s1024/Golden%252520Grass.jpg",
+            "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s1024/Grand%252520Teton.jpg",
+            "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s1024/Grass%252520Closeup.jpg",
+            "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s1024/Green%252520Grass.jpg",
+            "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s1024/Hanging%252520Leaf.jpg",
+            "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s1024/Highway%2525201.jpg",
+            "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s1024/Horseshoe%252520Bend%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s1024/Horseshoe%252520Bend.jpg",
+            "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s1024/Into%252520the%252520Blue.jpg",
+            "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s1024/Jelly%252520Fish%2525202.jpg",
+            "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s1024/Jelly%252520Fish%2525203.jpg",
+            "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s1024/Kauai.jpg",
+            "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s1024/Kyoto%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s1024/Lake%252520Tahoe%252520Colors.jpg",
+            "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s1024/Lava%252520from%252520the%252520Sky.jpg",
+            "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s1024/Leica%25252050mm%252520Summilux.jpg",
+            "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s1024/Leica%25252050mm%252520Summilux.jpg",
+            "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s1024/Leica%252520M8%252520%252528Front%252529.jpg",
+            "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s1024/Light%252520to%252520Sand.jpg",
+            "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s1024/Little%252520Bit%252520of%252520Paradise.jpg",
+            "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s1024/Lone%252520Pine%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s1024/Lonely%252520Rock.jpg",
+            "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s1024/Longue%252520Vue.jpg",
+            "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s1024/Look%252520Me%252520in%252520the%252520Eye.jpg",
+            "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s1024/Lost%252520in%252520a%252520Field.jpg",
+            "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s1024/Marshall%252520Beach%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s1024/Mono%252520Lake%252520Blue.jpg",
+            "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s1024/Monument%252520Valley%252520Overlook.jpg",
+            "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s1024/Moving%252520Rock.jpg",
+            "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s1024/Napali%252520Coast.jpg",
+            "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s1024/One%252520Wheel.jpg",
+            "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s1024/Open%252520Sky.jpg",
+            "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s1024/Orange%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s1024/Orchid.jpg",
+            "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s1024/Over%252520there.jpg",
+            "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s1024/Plumes.jpg",
+            "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s1024/Rainbokeh.jpg",
+            "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s1024/Rainbow.jpg",
+            "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s1024/Rice%252520Fields.jpg",
+            "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s1024/Rockaway%252520Fire%252520Sky.jpg",
+            "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s1024/Rockaway%252520Flow.jpg",
+            "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s1024/Rockaway%252520Sunset%252520Sky.jpg",
+            "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s1024/Russian%252520Ridge%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s1024/Rust%252520Knot.jpg",
+            "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s1024/Sailing%252520Stones.jpg",
+            "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s1024/Seahorse.jpg",
+            "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s1024/Shinjuku%252520Street.jpg",
+            "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s1024/Sierra%252520Heavens.jpg",
+            "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s1024/Sierra%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s1024/Sin%252520Lights.jpg",
+            "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s1024/Starry%252520Lake.jpg",
+            "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s1024/Starry%252520Night.jpg",
+            "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s1024/Stream.jpg",
+            "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s1024/Strip%252520Sunset.jpg",
+            "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s1024/Sunset%252520Hills.jpg",
+            "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s1024/Tenaya%252520Lake%2525202.jpg",
+            "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s1024/Tenaya%252520Lake.jpg",
+            "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s1024/The%252520Cave%252520BW.jpg",
+            "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s1024/The%252520Fisherman.jpg",
+            "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s1024/The%252520Night%252520is%252520Coming.jpg",
+            "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s1024/The%252520Road.jpg",
+            "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s1024/Tokyo%252520Heights.jpg",
+            "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s1024/Tokyo%252520Highway.jpg",
+            "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s1024/Tokyo%252520Smog.jpg",
+            "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s1024/Tufa%252520at%252520Night.jpg",
+            "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s1024/Valley%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s1024/Windmill%252520Sunrise.jpg",
+            "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s1024/Windmill.jpg",
+            "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s1024/Windmills.jpg",
+            "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s1024/Yet%252520Another%252520Rockaway%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s1024/Yosemite%252520Tree.jpg",
     };
 
     /**
      * This are PicasaWeb thumbnail URLs and could potentially change. Ideally the PicasaWeb API
      * should be used to fetch the URLs.
+     *
+     * Credit to Romain Guy for the photos:
+     * http://www.curious-creature.org/
+     * https://plus.google.com/109538161516040592207/about
+     * http://www.flickr.com/photos/romainguy
      */
     public final static String[] imageThumbUrls = new String[] {
-        "http://lh6.googleusercontent.com/-jZgveEqb6pg/T3R4kXScycI/AAAAAAAAAE0/xQ7CvpfXDzc/s160-c/sample_image_01.jpg",
-        "http://lh4.googleusercontent.com/-K2FMuOozxU0/T3R4lRAiBTI/AAAAAAAAAE8/a3Eh9JvnnzI/s160-c/sample_image_02.jpg",
-        "http://lh5.googleusercontent.com/-SCS5C646rxM/T3R4l7QB6xI/AAAAAAAAAFE/xLcuVv3CUyA/s160-c/sample_image_03.jpg",
-        "http://lh6.googleusercontent.com/-f0NJR6-_Thg/T3R4mNex2wI/AAAAAAAAAFI/45oug4VE8MI/s160-c/sample_image_04.jpg",
-        "http://lh3.googleusercontent.com/-n-xcJmiI0pg/T3R4mkSchHI/AAAAAAAAAFU/EoiNNb7kk3A/s160-c/sample_image_05.jpg",
-        "http://lh3.googleusercontent.com/-X43vAudm7f4/T3R4nGSChJI/AAAAAAAAAFk/3bna6D-2EE8/s160-c/sample_image_06.jpg",
-        "http://lh5.googleusercontent.com/-MpZneqIyjXU/T3R4nuGO1aI/AAAAAAAAAFg/r09OPjLx1ZY/s160-c/sample_image_07.jpg",
-        "http://lh6.googleusercontent.com/-ql3YNfdClJo/T3XvW9apmFI/AAAAAAAAAL4/_6HFDzbahc4/s160-c/sample_image_08.jpg",
-        "http://lh5.googleusercontent.com/-Pxa7eqF4cyc/T3R4oasvPEI/AAAAAAAAAF0/-uYDH92h8LA/s160-c/sample_image_09.jpg",
-        "http://lh4.googleusercontent.com/-Li-rjhFEuaI/T3R4o-VUl4I/AAAAAAAAAF8/5E5XdMnP1oE/s160-c/sample_image_10.jpg",
-        "http://lh5.googleusercontent.com/-_HU4fImgFhA/T3R4pPVIwWI/AAAAAAAAAGA/0RfK_Vkgth4/s160-c/sample_image_11.jpg",
-        "http://lh6.googleusercontent.com/-0gnNrVjwa0Y/T3R4peGYJwI/AAAAAAAAAGU/uX_9wvRPM9I/s160-c/sample_image_12.jpg",
-        "http://lh3.googleusercontent.com/-HBxuzALS_Zs/T3R4qERykaI/AAAAAAAAAGQ/_qQ16FaZ1q0/s160-c/sample_image_13.jpg",
-        "http://lh4.googleusercontent.com/-cKojDrARNjQ/T3R4qfWSGPI/AAAAAAAAAGY/MR5dnbNaPyY/s160-c/sample_image_14.jpg",
-        "http://lh3.googleusercontent.com/-WujkdYfcyZ8/T3R4qrIMGUI/AAAAAAAAAGk/277LIdgvnjg/s160-c/sample_image_15.jpg",
-        "http://lh6.googleusercontent.com/-FMHR7Vy3PgI/T3R4rOXlEKI/AAAAAAAAAGs/VeXrDNDBkaw/s160-c/sample_image_16.jpg",
-        "http://lh4.googleusercontent.com/-mrR0AJyNTH0/T3R4rZs6CuI/AAAAAAAAAG0/UE1wQqCOqLA/s160-c/sample_image_17.jpg",
-        "http://lh6.googleusercontent.com/-z77w0eh3cow/T3R4rnLn05I/AAAAAAAAAG4/BaerfWoNucU/s160-c/sample_image_18.jpg",
-        "http://lh5.googleusercontent.com/-aWVwh1OU5Bk/T3R4sAWw0yI/AAAAAAAAAHE/4_KAvJttFwA/s160-c/sample_image_19.jpg",
-        "http://lh6.googleusercontent.com/-q-js52DMnWQ/T3R4tZhY2sI/AAAAAAAAAHM/A8kjp2Ivdqg/s160-c/sample_image_20.jpg",
-        "http://lh5.googleusercontent.com/-_jIzvvzXKn4/T3R4t7xpdVI/AAAAAAAAAHU/7QC6eZ10jgs/s160-c/sample_image_21.jpg",
-        "http://lh3.googleusercontent.com/-lnGi4IMLpwU/T3R4uCMa7vI/AAAAAAAAAHc/1zgzzz6qTpk/s160-c/sample_image_22.jpg",
-        "http://lh5.googleusercontent.com/-fFCzKjFPsPc/T3R4u0SZPFI/AAAAAAAAAHk/sbgjzrktOK0/s160-c/sample_image_23.jpg",
-        "http://lh4.googleusercontent.com/-8TqoW5gBE_Y/T3R4vBS3NPI/AAAAAAAAAHs/EZYvpNsaNXk/s160-c/sample_image_24.jpg",
-        "http://lh6.googleusercontent.com/-gc4eQ3ySdzs/T3R4vafoA7I/AAAAAAAAAH4/yKii5P6tqDE/s160-c/sample_image_25.jpg",
-        "http://lh5.googleusercontent.com/--NYOPCylU7Q/T3R4vjAiWkI/AAAAAAAAAH8/IPNx5q3ptRA/s160-c/sample_image_26.jpg",
-        "http://lh6.googleusercontent.com/-9IJM8so4vCI/T3R4vwJO2yI/AAAAAAAAAIE/ljlr-cwuqZM/s160-c/sample_image_27.jpg",
-        "http://lh4.googleusercontent.com/-KW6QwOHfhBs/T3R4w0RsQiI/AAAAAAAAAIM/uEFLVgHPFCk/s160-c/sample_image_28.jpg",
-        "http://lh4.googleusercontent.com/-z2557Ec1ctY/T3R4x3QA2hI/AAAAAAAAAIk/9-GzPL1lTWE/s160-c/sample_image_29.jpg",
-        "http://lh5.googleusercontent.com/-LaKXAn4Kr1c/T3R4yc5b4lI/AAAAAAAAAIY/fMgcOVQfmD0/s160-c/sample_image_30.jpg",
-        "http://lh4.googleusercontent.com/-F9LRToJoQdo/T3R4yrLtyQI/AAAAAAAAAIg/ri9uUCWuRmo/s160-c/sample_image_31.jpg",
-        "http://lh4.googleusercontent.com/-6X-xBwP-QpI/T3R4zGVboII/AAAAAAAAAIs/zYH4PjjngY0/s160-c/sample_image_32.jpg",
-        "http://lh5.googleusercontent.com/-VdLRjbW4LAs/T3R4zXu3gUI/AAAAAAAAAIw/9aFp9t7mCPg/s160-c/sample_image_33.jpg",
-        "http://lh6.googleusercontent.com/-gL6R17_fDJU/T3R4zpIXGjI/AAAAAAAAAI8/Q2Vjx-L9X20/s160-c/sample_image_34.jpg",
-        "http://lh3.googleusercontent.com/-1fGH4YJXEzo/T3R40Y1B7KI/AAAAAAAAAJE/MnTsa77g-nk/s160-c/sample_image_35.jpg",
-        "http://lh4.googleusercontent.com/-Ql0jHSrea-A/T3R403mUfFI/AAAAAAAAAJM/qzI4SkcH9tY/s160-c/sample_image_36.jpg",
-        "http://lh5.googleusercontent.com/-BL5FIBR_tzI/T3R41DA0AKI/AAAAAAAAAJk/GZfeeb-SLM0/s160-c/sample_image_37.jpg",
-        "http://lh4.googleusercontent.com/-wF2Vc9YDutw/T3R41fR2BCI/AAAAAAAAAJc/JdU1sHdMRAk/s160-c/sample_image_38.jpg",
-        "http://lh6.googleusercontent.com/-ZWHiPehwjTI/T3R41zuaKCI/AAAAAAAAAJg/hR3QJ1v3REg/s160-c/sample_image_39.jpg",
+            "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s160-c/A%252520Photographer.jpg",
+            "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s160-c/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg",
+            "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s160-c/Another%252520Rockaway%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s160-c/Antelope%252520Butte.jpg",
+            "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s160-c/Antelope%252520Hallway.jpg",
+            "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s160-c/Antelope%252520Walls.jpg",
+            "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s160-c/Apre%2525CC%252580s%252520la%252520Pluie.jpg",
+            "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s160-c/Backlit%252520Cloud.jpg",
+            "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s160-c/Bee%252520and%252520Flower.jpg",
+            "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s160-c/Bonzai%252520Rock%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s160-c/Caterpillar.jpg",
+            "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s160-c/Chess.jpg",
+            "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s160-c/Chihuly.jpg",
+            "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s160-c/Closed%252520Door.jpg",
+            "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s160-c/Colorado%252520River%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s160-c/Colors%252520of%252520Autumn.jpg",
+            "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s160-c/Countryside.jpg",
+            "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s160-c/Death%252520Valley%252520-%252520Dunes.jpg",
+            "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s160-c/Delicate%252520Arch.jpg",
+            "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s160-c/Despair.jpg",
+            "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s160-c/Eagle%252520Fall%252520Sunrise.jpg",
+            "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s160-c/Electric%252520Storm.jpg",
+            "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s160-c/False%252520Kiva.jpg",
+            "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s160-c/Fitzgerald%252520Streaks.jpg",
+            "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s160-c/Foggy%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s160-c/Frantic.jpg",
+            "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s160-c/Golden%252520Gate%252520Afternoon.jpg",
+            "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s160-c/Golden%252520Gate%252520Fog.jpg",
+            "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s160-c/Golden%252520Grass.jpg",
+            "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s160-c/Grand%252520Teton.jpg",
+            "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s160-c/Grass%252520Closeup.jpg",
+            "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s160-c/Green%252520Grass.jpg",
+            "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s160-c/Hanging%252520Leaf.jpg",
+            "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s160-c/Highway%2525201.jpg",
+            "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s160-c/Horseshoe%252520Bend%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s160-c/Horseshoe%252520Bend.jpg",
+            "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s160-c/Into%252520the%252520Blue.jpg",
+            "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s160-c/Jelly%252520Fish%2525202.jpg",
+            "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s160-c/Jelly%252520Fish%2525203.jpg",
+            "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s160-c/Kauai.jpg",
+            "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s160-c/Kyoto%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s160-c/Lake%252520Tahoe%252520Colors.jpg",
+            "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s160-c/Lava%252520from%252520the%252520Sky.jpg",
+            "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s160-c/Leica%25252050mm%252520Summilux.jpg",
+            "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s160-c/Leica%25252050mm%252520Summilux.jpg",
+            "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s160-c/Leica%252520M8%252520%252528Front%252529.jpg",
+            "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s160-c/Light%252520to%252520Sand.jpg",
+            "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s160-c/Little%252520Bit%252520of%252520Paradise.jpg",
+            "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s160-c/Lone%252520Pine%252520Sunset.jpg",
+            "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s160-c/Lonely%252520Rock.jpg",
+            "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s160-c/Longue%252520Vue.jpg",
+            "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s160-c/Look%252520Me%252520in%252520the%252520Eye.jpg",
+            "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s160-c/Lost%252520in%252520a%252520Field.jpg",
+            "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s160-c/Marshall%252520Beach%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s160-c/Mono%252520Lake%252520Blue.jpg",
+            "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s160-c/Monument%252520Valley%252520Overlook.jpg",
+            "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s160-c/Moving%252520Rock.jpg",
+            "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s160-c/Napali%252520Coast.jpg",
+            "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s160-c/One%252520Wheel.jpg",
+            "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s160-c/Open%252520Sky.jpg",
+            "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s160-c/Orange%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s160-c/Orchid.jpg",
+            "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s160-c/Over%252520there.jpg",
+            "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s160-c/Plumes.jpg",
+            "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s160-c/Rainbokeh.jpg",
+            "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s160-c/Rainbow.jpg",
+            "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s160-c/Rice%252520Fields.jpg",
+            "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s160-c/Rockaway%252520Fire%252520Sky.jpg",
+            "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s160-c/Rockaway%252520Flow.jpg",
+            "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s160-c/Rockaway%252520Sunset%252520Sky.jpg",
+            "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s160-c/Russian%252520Ridge%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s160-c/Rust%252520Knot.jpg",
+            "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s160-c/Sailing%252520Stones.jpg",
+            "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s160-c/Seahorse.jpg",
+            "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s160-c/Shinjuku%252520Street.jpg",
+            "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s160-c/Sierra%252520Heavens.jpg",
+            "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s160-c/Sierra%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s160-c/Sin%252520Lights.jpg",
+            "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s160-c/Starry%252520Lake.jpg",
+            "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s160-c/Starry%252520Night.jpg",
+            "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s160-c/Stream.jpg",
+            "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s160-c/Strip%252520Sunset.jpg",
+            "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s160-c/Sunset%252520Hills.jpg",
+            "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s160-c/Tenaya%252520Lake%2525202.jpg",
+            "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s160-c/Tenaya%252520Lake.jpg",
+            "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s160-c/The%252520Cave%252520BW.jpg",
+            "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s160-c/The%252520Fisherman.jpg",
+            "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s160-c/The%252520Night%252520is%252520Coming.jpg",
+            "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s160-c/The%252520Road.jpg",
+            "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s160-c/Tokyo%252520Heights.jpg",
+            "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s160-c/Tokyo%252520Highway.jpg",
+            "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s160-c/Tokyo%252520Smog.jpg",
+            "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s160-c/Tufa%252520at%252520Night.jpg",
+            "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s160-c/Valley%252520Sunset.jpg",
+            "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s160-c/Windmill%252520Sunrise.jpg",
+            "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s160-c/Windmill.jpg",
+            "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s160-c/Windmills.jpg",
+            "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s160-c/Yet%252520Another%252520Rockaway%252520Sunset.jpg",
+            "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s160-c/Yosemite%252520Tree.jpg",
     };
 }
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailActivity.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailActivity.java
index acd483f..5fb0776 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailActivity.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailActivity.java
@@ -73,7 +73,7 @@
 
         ImageCache.ImageCacheParams cacheParams =
                 new ImageCache.ImageCacheParams(this, IMAGE_CACHE_DIR);
-        cacheParams.setMemCacheSizePercent(this, 0.25f); // Set memory cache to 25% of mem class
+        cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
 
         // The ImageFetcher takes care of loading images into our ImageView children asynchronously
         mImageFetcher = new ImageFetcher(this, longest);
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailFragment.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailFragment.java
index a0b3855..9fff8a0 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailFragment.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageDetailFragment.java
@@ -18,7 +18,6 @@
 
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageGridFragment.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageGridFragment.java
index 8a8bcf0..9076cd9 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageGridFragment.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/ImageGridFragment.java
@@ -79,8 +79,7 @@
 
         ImageCacheParams cacheParams = new ImageCacheParams(getActivity(), IMAGE_CACHE_DIR);
 
-        // Set memory cache to 25% of mem class
-        cacheParams.setMemCacheSizePercent(getActivity(), 0.25f);
+        cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
 
         // The ImageFetcher takes care of loading images into our ImageView children asynchronously
         mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize);
@@ -150,6 +149,7 @@
     @Override
     public void onPause() {
         super.onPause();
+        mImageFetcher.setPauseWork(false);
         mImageFetcher.setExitTasksEarly(true);
         mImageFetcher.flushCache();
     }
@@ -270,7 +270,7 @@
             // Now handle the main ImageView thumbnails
             ImageView imageView;
             if (convertView == null) { // if it's not recycled, instantiate and initialize
-                imageView = new ImageView(mContext);
+                imageView = new RecyclingImageView(mContext);
                 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                 imageView.setLayoutParams(mImageViewLayoutParams);
             } else { // Otherwise re-use the converted view
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/RecyclingImageView.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/RecyclingImageView.java
new file mode 100644
index 0000000..1bcc014
--- /dev/null
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/ui/RecyclingImageView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.example.android.bitmapfun.ui;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.example.android.bitmapfun.util.RecyclingBitmapDrawable;
+
+/**
+ * Sub-class of ImageView which automatically notifies the drawable when it is
+ * being displayed.
+ */
+public class RecyclingImageView extends ImageView {
+
+    public RecyclingImageView(Context context) {
+        super(context);
+    }
+
+    public RecyclingImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * @see android.widget.ImageView#onDetachedFromWindow()
+     */
+    @Override
+    protected void onDetachedFromWindow() {
+        // This has been detached from Window, so clear the drawable
+        setImageDrawable(null);
+
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
+     */
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        // Keep hold of previous Drawable
+        final Drawable previousDrawable = getDrawable();
+
+        // Call super to set new Drawable
+        super.setImageDrawable(drawable);
+
+        // Notify new Drawable that it is being displayed
+        notifyDrawable(drawable, true);
+
+        // Notify old Drawable so it is no longer being displayed
+        notifyDrawable(previousDrawable, false);
+    }
+
+    /**
+     * Notifies the drawable that it's displayed state has changed.
+     *
+     * @param drawable
+     * @param isDisplayed
+     */
+    private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
+        if (drawable instanceof RecyclingBitmapDrawable) {
+            // The drawable is a CountingBitmapDrawable, so notify it
+            ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
+        } else if (drawable instanceof LayerDrawable) {
+            // The drawable is a LayerDrawable, so recurse on each layer
+            LayerDrawable layerDrawable = (LayerDrawable) drawable;
+            for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
+                notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
+            }
+        }
+    }
+
+}
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
index 6995eab..1459c12 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageCache.java
@@ -16,12 +16,14 @@
 
 package com.example.android.bitmapfun.util;
 
+import com.example.android.bitmapfun.BuildConfig;
+
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.StatFs;
@@ -30,25 +32,32 @@
 import android.support.v4.util.LruCache;
 import android.util.Log;
 
-import com.example.android.bitmapfun.BuildConfig;
-
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.ref.SoftReference;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
 
 /**
- * This class holds our bitmap caches (memory and disk).
+ * This class handles disk and memory caching of bitmaps in conjunction with the
+ * {@link ImageWorker} class and its subclasses. Use
+ * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)} to get an instance of this
+ * class, although usually a cache should be added directly to an {@link ImageWorker} by calling
+ * {@link ImageWorker#addImageCache(FragmentManager, ImageCacheParams)}.
  */
 public class ImageCache {
     private static final String TAG = "ImageCache";
 
-    // Default memory cache size
-    private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 5; // 5MB
+    // Default memory cache size in kilobytes
+    private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
 
-    // Default disk cache size
+    // Default disk cache size in bytes
     private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
 
     // Compression settings when writing images to disk cache
@@ -59,43 +68,37 @@
     // Constants to easily toggle various caches
     private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
     private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
-    private static final boolean DEFAULT_CLEAR_DISK_CACHE_ON_START = false;
     private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
 
     private DiskLruCache mDiskLruCache;
-    private LruCache<String, Bitmap> mMemoryCache;
+    private LruCache<String, BitmapDrawable> mMemoryCache;
     private ImageCacheParams mCacheParams;
     private final Object mDiskCacheLock = new Object();
     private boolean mDiskCacheStarting = true;
 
+    private HashSet<SoftReference<Bitmap>> mReusableBitmaps;
+
     /**
-     * Creating a new ImageCache object using the specified parameters.
+     * Create a new ImageCache object using the specified parameters. This should not be
+     * called directly by other classes, instead use
+     * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)} to fetch an ImageCache
+     * instance.
      *
      * @param cacheParams The cache parameters to use to initialize the cache
      */
-    public ImageCache(ImageCacheParams cacheParams) {
+    private ImageCache(ImageCacheParams cacheParams) {
         init(cacheParams);
     }
 
     /**
-     * Creating a new ImageCache object using the default parameters.
-     *
-     * @param context The context to use
-     * @param uniqueName A unique name that will be appended to the cache directory
-     */
-    public ImageCache(Context context, String uniqueName) {
-        init(new ImageCacheParams(context, uniqueName));
-    }
-
-    /**
-     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
-     * one is created using the supplied params and saved to a {@link RetainFragment}.
+     * Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the
+     * ImageCache object across configuration changes such as a change in device orientation.
      *
      * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
-     * @param cacheParams The cache parameters to use if creating the ImageCache
+     * @param cacheParams The cache parameters to use if the ImageCache needs instantiation.
      * @return An existing retained ImageCache object or a new one if one did not exist
      */
-    public static ImageCache findOrCreateCache(
+    public static ImageCache getInstance(
             FragmentManager fragmentManager, ImageCacheParams cacheParams) {
 
         // Search for, or create an instance of the non-UI RetainFragment
@@ -126,14 +129,43 @@
             if (BuildConfig.DEBUG) {
                 Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
             }
-            mMemoryCache = new LruCache<String, Bitmap>(mCacheParams.memCacheSize) {
+
+            // If we're running on Honeycomb or newer, then
+            if (Utils.hasHoneycomb()) {
+                mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
+            }
+
+            mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
+
                 /**
-                 * Measure item size in bytes rather than units which is more practical
+                 * Notify the removed entry that is no longer being cached
+                 */
+                @Override
+                protected void entryRemoved(boolean evicted, String key,
+                        BitmapDrawable oldValue, BitmapDrawable newValue) {
+                    if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
+                        // The removed entry is a recycling drawable, so notify it 
+                        // that it has been removed from the memory cache
+                        ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+                    } else {
+                        // The removed entry is a standard BitmapDrawable
+
+                        if (Utils.hasHoneycomb()) {
+                            // We're running on Honeycomb or later, so add the bitmap
+                            // to a SoftRefrence set for possible use with inBitmap later
+                            mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
+                        }
+                    }
+                }
+
+                /**
+                 * Measure item size in kilobytes rather than units which is more practical
                  * for a bitmap cache
                  */
                 @Override
-                protected int sizeOf(String key, Bitmap bitmap) {
-                    return getBitmapSize(bitmap);
+                protected int sizeOf(String key, BitmapDrawable value) {
+                    final int bitmapSize = getBitmapSize(value) / 1024;
+                    return bitmapSize == 0 ? 1 : bitmapSize;
                 }
             };
         }
@@ -183,16 +215,21 @@
     /**
      * Adds a bitmap to both memory and disk cache.
      * @param data Unique identifier for the bitmap to store
-     * @param bitmap The bitmap to store
+     * @param value The bitmap drawable to store
      */
-    public void addBitmapToCache(String data, Bitmap bitmap) {
-        if (data == null || bitmap == null) {
+    public void addBitmapToCache(String data, BitmapDrawable value) {
+        if (data == null || value == null) {
             return;
         }
 
         // Add to memory cache
-        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
-            mMemoryCache.put(data, bitmap);
+        if (mMemoryCache != null) {
+            if (RecyclingBitmapDrawable.class.isInstance(value)) {
+                // The removed entry is a recycling drawable, so notify it 
+                // that it has been added into the memory cache
+                ((RecyclingBitmapDrawable) value).setIsCached(true);
+            }
+            mMemoryCache.put(data, value);
         }
 
         synchronized (mDiskCacheLock) {
@@ -206,7 +243,7 @@
                         final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                         if (editor != null) {
                             out = editor.newOutputStream(DISK_CACHE_INDEX);
-                            bitmap.compress(
+                            value.getBitmap().compress(
                                     mCacheParams.compressFormat, mCacheParams.compressQuality, out);
                             editor.commit();
                             out.close();
@@ -233,19 +270,20 @@
      * Get from memory cache.
      *
      * @param data Unique identifier for which item to get
-     * @return The bitmap if found in cache, null otherwise
+     * @return The bitmap drawable if found in cache, null otherwise
      */
-    public Bitmap getBitmapFromMemCache(String data) {
+    public BitmapDrawable getBitmapFromMemCache(String data) {
+        BitmapDrawable memValue = null;
+
         if (mMemoryCache != null) {
-            final Bitmap memBitmap = mMemoryCache.get(data);
-            if (memBitmap != null) {
-                if (BuildConfig.DEBUG) {
-                    Log.d(TAG, "Memory cache hit");
-                }
-                return memBitmap;
-            }
+            memValue = mMemoryCache.get(data);
         }
-        return null;
+
+        if (BuildConfig.DEBUG && memValue != null) {
+            Log.d(TAG, "Memory cache hit");
+        }
+
+        return memValue;
     }
 
     /**
@@ -256,6 +294,8 @@
      */
     public Bitmap getBitmapFromDiskCache(String data) {
         final String key = hashKeyForDisk(data);
+        Bitmap bitmap = null;
+
         synchronized (mDiskCacheLock) {
             while (mDiskCacheStarting) {
                 try {
@@ -272,8 +312,12 @@
                         }
                         inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
                         if (inputStream != null) {
-                            final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
-                            return bitmap;
+                            FileDescriptor fd = ((FileInputStream) inputStream).getFD();
+
+                            // Decode bitmap, but we don't want to sample so give
+                            // MAX_VALUE as the target dimensions
+                            bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
+                                    fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
                         }
                     }
                 } catch (final IOException e) {
@@ -286,11 +330,44 @@
                     } catch (IOException e) {}
                 }
             }
-            return null;
+            return bitmap;
         }
     }
 
     /**
+     * @param options - BitmapFactory.Options with out* options populated
+     * @return Bitmap that case be used for inBitmap
+     */
+    protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+        Bitmap bitmap = null;
+
+        if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+            final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
+            Bitmap item;
+
+            while (iterator.hasNext()) {
+                item = iterator.next().get();
+
+                if (null != item && item.isMutable()) {
+                    // Check to see it the item can be used for inBitmap
+                    if (canUseForInBitmap(item, options)) {
+                        bitmap = item;
+
+                        // Remove from reusable set so it can't be used again
+                        iterator.remove();
+                        break;
+                    }
+                } else {
+                    // Remove from the set if the reference has been cleared.
+                    iterator.remove();
+                }
+            }
+        }
+
+        return bitmap;
+    }
+
+    /**
      * Clears both the memory and disk cache associated with this ImageCache object. Note that
      * this includes disk access so this should not be executed on the main/UI thread.
      */
@@ -371,41 +448,55 @@
         public int compressQuality = DEFAULT_COMPRESS_QUALITY;
         public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
         public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
-        public boolean clearDiskCacheOnStart = DEFAULT_CLEAR_DISK_CACHE_ON_START;
         public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
 
-        public ImageCacheParams(Context context, String uniqueName) {
-            diskCacheDir = getDiskCacheDir(context, uniqueName);
-        }
-
-        public ImageCacheParams(File diskCacheDir) {
-            this.diskCacheDir = diskCacheDir;
+        /**
+         * Create a set of image cache parameters that can be provided to
+         * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)} or
+         * {@link ImageWorker#addImageCache(FragmentManager, ImageCacheParams)}.
+         * @param context A context to use.
+         * @param diskCacheDirectoryName A unique subdirectory name that will be appended to the
+         *                               application cache directory. Usually "cache" or "images"
+         *                               is sufficient.
+         */
+        public ImageCacheParams(Context context, String diskCacheDirectoryName) {
+            diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
         }
 
         /**
-         * Sets the memory cache size based on a percentage of the device memory class.
-         * Eg. setting percent to 0.2 would set the memory cache to one fifth of the device memory
-         * class. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
+         * Sets the memory cache size based on a percentage of the max available VM memory.
+         * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
+         * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
+         * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
+         * to construct a LruCache which takes an int in its constructor.
          *
          * This value should be chosen carefully based on a number of factors
          * Refer to the corresponding Android Training class for more discussion:
          * http://developer.android.com/training/displaying-bitmaps/
          *
-         * @param context Context to use to fetch memory class
-         * @param percent Percent of memory class to use to size memory cache
+         * @param percent Percent of available app memory to use to size memory cache
          */
-        public void setMemCacheSizePercent(Context context, float percent) {
+        public void setMemCacheSizePercent(float percent) {
             if (percent < 0.05f || percent > 0.8f) {
                 throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
                         + "between 0.05 and 0.8 (inclusive)");
             }
-            memCacheSize = Math.round(percent * getMemoryClass(context) * 1024 * 1024);
+            memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
         }
+    }
 
-        private static int getMemoryClass(Context context) {
-            return ((ActivityManager) context.getSystemService(
-                    Context.ACTIVITY_SERVICE)).getMemoryClass();
-        }
+    /**
+     * @param candidate - Bitmap to check
+     * @param targetOptions - Options that have the out* value populated
+     * @return true if <code>candidate</code> can be used for inBitmap re-use with
+     *      <code>targetOptions</code>
+     */
+    private static boolean canUseForInBitmap(
+            Bitmap candidate, BitmapFactory.Options targetOptions) {
+        int width = targetOptions.outWidth / targetOptions.inSampleSize;
+        int height = targetOptions.outHeight / targetOptions.inSampleSize;
+
+        return candidate.getWidth() == width && candidate.getHeight() == height;
     }
 
     /**
@@ -456,12 +547,14 @@
     }
 
     /**
-     * Get the size in bytes of a bitmap.
-     * @param bitmap
+     * Get the size in bytes of a bitmap in a BitmapDrawable.
+     * @param value
      * @return size in bytes
      */
     @TargetApi(12)
-    public static int getBitmapSize(Bitmap bitmap) {
+    public static int getBitmapSize(BitmapDrawable value) {
+        Bitmap bitmap = value.getBitmap();
+
         if (Utils.hasHoneycombMR1()) {
             return bitmap.getByteCount();
         }
@@ -523,7 +616,7 @@
      * @return The existing instance of the Fragment or the new instance if just
      *         created.
      */
-    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
+    private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
         // Check to see if we have retained the worker fragment.
         RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
 
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
index 7084845..4c92d74 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageFetcher.java
@@ -241,7 +241,8 @@
 
         Bitmap bitmap = null;
         if (fileDescriptor != null) {
-            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, mImageHeight);
+            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
+                    mImageHeight, getImageCache());
         }
         if (fileInputStream != null) {
             try {
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
index f533231..2a9d152 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageResizer.java
@@ -16,10 +16,12 @@
 
 package com.example.android.bitmapfun.util;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Build;
 import android.util.Log;
 
 import com.example.android.bitmapfun.BuildConfig;
@@ -90,7 +92,8 @@
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "processBitmap - " + resId);
         }
-        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, mImageHeight);
+        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
+                mImageHeight, getImageCache());
     }
 
     @Override
@@ -105,11 +108,12 @@
      * @param resId The resource id of the image data
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
-            int reqWidth, int reqHeight) {
+            int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -119,6 +123,11 @@
         // Calculate inSampleSize
         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
         return BitmapFactory.decodeResource(res, resId, options);
@@ -130,11 +139,12 @@
      * @param filename The full path of the file to decode
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromFile(String filename,
-            int reqWidth, int reqHeight) {
+            int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -144,6 +154,11 @@
         // Calculate inSampleSize
         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
         return BitmapFactory.decodeFile(filename, options);
@@ -155,11 +170,12 @@
      * @param fileDescriptor The file descriptor to read from
      * @param reqWidth The requested width of the resulting bitmap
      * @param reqHeight The requested height of the resulting bitmap
+     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
      * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
      *         that are equal to or greater than the requested width and height
      */
     public static Bitmap decodeSampledBitmapFromDescriptor(
-            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
+            FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
 
         // First decode with inJustDecodeBounds=true to check dimensions
         final BitmapFactory.Options options = new BitmapFactory.Options();
@@ -171,9 +187,34 @@
 
         // Decode bitmap with inSampleSize set
         options.inJustDecodeBounds = false;
+
+        // If we're running on Honeycomb or newer, try to use inBitmap
+        if (Utils.hasHoneycomb()) {
+            addInBitmapOptions(options, cache);
+        }
+
         return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
     }
 
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
+        // inBitmap only works with mutable bitmaps so force the decoder to
+        // return mutable bitmaps.
+        options.inMutable = true;
+
+        if (cache != null) {
+            // Try and find a bitmap to use for inBitmap
+            Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+            if (inBitmap != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "Found bitmap to use for inBitmap");
+                }
+                options.inBitmap = inBitmap;
+            }
+        }
+    }
+
     /**
      * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
      * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
@@ -196,23 +237,24 @@
         int inSampleSize = 1;
 
         if (height > reqHeight || width > reqWidth) {
-            if (width > height) {
-                inSampleSize = Math.round((float) height / (float) reqHeight);
-            } else {
-                inSampleSize = Math.round((float) width / (float) reqWidth);
-            }
+
+            // Calculate ratios of height and width to requested height and width
+            final int heightRatio = Math.round((float) height / (float) reqHeight);
+            final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
+            // with both dimensions larger than or equal to the requested height and width.
+            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
 
             // This offers some additional logic in case the image has a strange
             // aspect ratio. For example, a panorama may have a much larger
             // width than height. In these cases the total pixels might still
             // end up being too large to fit comfortably in memory, so we should
-            // be more aggressive with sample down the image (=larger
-            // inSampleSize).
+            // be more aggressive with sample down the image (=larger inSampleSize).
 
             final float totalPixels = width * height;
 
-            // Anything more than 2x the requested pixels we'll sample down
-            // further.
+            // Anything more than 2x the requested pixels we'll sample down further
             final float totalReqPixelsCap = reqWidth * reqHeight * 2;
 
             while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
index 32c43b2..d260660 100644
--- a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/ImageWorker.java
@@ -16,6 +16,8 @@
 
 package com.example.android.bitmapfun.util;
 
+import com.example.android.bitmapfun.BuildConfig;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -24,12 +26,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
+import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.util.Log;
 import android.widget.ImageView;
 
-import com.example.android.bitmapfun.BuildConfig;
-
 import java.lang.ref.WeakReference;
 
 /**
@@ -62,11 +63,11 @@
 
     /**
      * Load an image specified by the data parameter into an ImageView (override
-     * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and disk
-     * cache will be used if an {@link ImageCache} has been set using
-     * {@link ImageWorker#setImageCache(ImageCache)}. If the image is found in the memory cache, it
-     * is set immediately, otherwise an {@link AsyncTask} will be created to asynchronously load the
-     * bitmap.
+     * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
+     * disk cache will be used if an {@link ImageCache} has been added using
+     * {@link ImageWorker#addImageCache(FragmentManager, ImageCache.ImageCacheParams)}. If the
+     * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
+     * will be created to asynchronously load the bitmap.
      *
      * @param data The URL of the image to download.
      * @param imageView The ImageView to bind the downloaded image to.
@@ -76,15 +77,15 @@
             return;
         }
 
-        Bitmap bitmap = null;
+        BitmapDrawable value = null;
 
         if (mImageCache != null) {
-            bitmap = mImageCache.getBitmapFromMemCache(String.valueOf(data));
+            value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
         }
 
-        if (bitmap != null) {
+        if (value != null) {
             // Bitmap found in memory cache
-            imageView.setImageBitmap(bitmap);
+            imageView.setImageDrawable(value);
         } else if (cancelPotentialWork(data, imageView)) {
             final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
             final AsyncDrawable asyncDrawable =
@@ -117,28 +118,29 @@
     }
 
     /**
-     * Adds an {@link ImageCache} to this worker in the background (to prevent disk access on UI
-     * thread).
+     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
+     * caching.
      * @param fragmentManager
-     * @param cacheParams
+     * @param cacheParams The cache parameters to use for the image cache.
      */
     public void addImageCache(FragmentManager fragmentManager,
             ImageCache.ImageCacheParams cacheParams) {
         mImageCacheParams = cacheParams;
-        setImageCache(ImageCache.findOrCreateCache(fragmentManager, mImageCacheParams));
+        mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
         new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
     }
 
     /**
-     * Sets the {@link ImageCache} object to use with this ImageWorker. Usually you will not need
-     * to call this directly, instead use {@link ImageWorker#addImageCache} which will create and
-     * add the {@link ImageCache} object in a background thread (to ensure no disk access on the
-     * main/UI thread).
-     *
-     * @param imageCache
+     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
+     * caching.
+     * @param activity
+     * @param diskCacheDirectoryName See
+     * {@link ImageCache.ImageCacheParams#ImageCacheParams(Context, String)}.
      */
-    public void setImageCache(ImageCache imageCache) {
-        mImageCache = imageCache;
+    public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
+        mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
+        mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
+        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
     }
 
     /**
@@ -150,6 +152,7 @@
 
     public void setExitTasksEarly(boolean exitTasksEarly) {
         mExitTasksEarly = exitTasksEarly;
+        setPauseWork(false);
     }
 
     /**
@@ -164,6 +167,13 @@
     protected abstract Bitmap processBitmap(Object data);
 
     /**
+     * @return The {@link ImageCache} object currently being used by this ImageWorker.
+     */
+    protected ImageCache getImageCache() {
+        return mImageCache;
+    }
+
+    /**
      * Cancels any pending work attached to the provided ImageView.
      * @param imageView
      */
@@ -221,7 +231,7 @@
     /**
      * The actual AsyncTask that will asynchronously process the image.
      */
-    private class BitmapWorkerTask extends AsyncTask<Object, Void, Bitmap> {
+    private class BitmapWorkerTask extends AsyncTask<Object, Void, BitmapDrawable> {
         private Object data;
         private final WeakReference<ImageView> imageViewReference;
 
@@ -233,7 +243,7 @@
          * Background processing.
          */
         @Override
-        protected Bitmap doInBackground(Object... params) {
+        protected BitmapDrawable doInBackground(Object... params) {
             if (BuildConfig.DEBUG) {
                 Log.d(TAG, "doInBackground - starting work");
             }
@@ -241,6 +251,7 @@
             data = params[0];
             final String dataString = String.valueOf(data);
             Bitmap bitmap = null;
+            BitmapDrawable drawable = null;
 
             // Wait here if work is paused and the task is not cancelled
             synchronized (mPauseWorkLock) {
@@ -273,39 +284,50 @@
             // bitmap to the cache for future use. Note we don't check if the task was cancelled
             // here, if it was, and the thread is still running, we may as well add the processed
             // bitmap to our cache as it might be used again in the future
-            if (bitmap != null && mImageCache != null) {
-                mImageCache.addBitmapToCache(dataString, bitmap);
+            if (bitmap != null) {
+                if (Utils.hasHoneycomb()) {
+                    // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
+                    drawable = new BitmapDrawable(mResources, bitmap);
+                } else {
+                    // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
+                    // which will recycle automagically
+                    drawable = new RecyclingBitmapDrawable(mResources, bitmap);
+                }
+
+                if (mImageCache != null) {
+                    mImageCache.addBitmapToCache(dataString, drawable);
+                }
             }
 
             if (BuildConfig.DEBUG) {
                 Log.d(TAG, "doInBackground - finished work");
             }
 
-            return bitmap;
+            return drawable;
         }
 
         /**
          * Once the image is processed, associates it to the imageView
          */
         @Override
-        protected void onPostExecute(Bitmap bitmap) {
+        protected void onPostExecute(BitmapDrawable value) {
             // if cancel was called on this task or the "exit early" flag is set then we're done
             if (isCancelled() || mExitTasksEarly) {
-                bitmap = null;
+                value = null;
             }
 
             final ImageView imageView = getAttachedImageView();
-            if (bitmap != null && imageView != null) {
+            if (value != null && imageView != null) {
                 if (BuildConfig.DEBUG) {
                     Log.d(TAG, "onPostExecute - setting bitmap");
                 }
-                setImageBitmap(imageView, bitmap);
+                setImageDrawable(imageView, value);
             }
         }
 
         @Override
-        protected void onCancelled(Bitmap bitmap) {
-            super.onCancelled(bitmap);
+        protected void onCancelled(BitmapDrawable value) {
+            super.onCancelled(value);
             synchronized (mPauseWorkLock) {
                 mPauseWorkLock.notifyAll();
             }
@@ -348,18 +370,19 @@
     }
 
     /**
-     * Called when the processing is complete and the final bitmap should be set on the ImageView.
+     * Called when the processing is complete and the final drawable should be 
+     * set on the ImageView.
      *
      * @param imageView
-     * @param bitmap
+     * @param drawable
      */
-    private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
+    private void setImageDrawable(ImageView imageView, Drawable drawable) {
         if (mFadeInBitmap) {
-            // Transition drawable with a transparent drwabale and the final bitmap
+            // Transition drawable with a transparent drawable and the final drawable
             final TransitionDrawable td =
                     new TransitionDrawable(new Drawable[] {
                             new ColorDrawable(android.R.color.transparent),
-                            new BitmapDrawable(mResources, bitmap)
+                            drawable
                     });
             // Set background to loading bitmap
             imageView.setBackgroundDrawable(
@@ -368,10 +391,22 @@
             imageView.setImageDrawable(td);
             td.startTransition(FADE_IN_TIME);
         } else {
-            imageView.setImageBitmap(bitmap);
+            imageView.setImageDrawable(drawable);
         }
     }
 
+    /**
+     * Pause any ongoing background work. This can be used as a temporary
+     * measure to improve performance. For example background work could
+     * be paused when a ListView or GridView is being scrolled using a
+     * {@link android.widget.AbsListView.OnScrollListener} to keep
+     * scrolling smooth.
+     * <p>
+     * If work is paused, be sure setPauseWork(false) is called again
+     * before your fragment or activity is destroyed (for example during
+     * {@link android.app.Activity#onPause()}), or there is a risk the
+     * background thread will never finish.
+     */
     public void setPauseWork(boolean pauseWork) {
         synchronized (mPauseWorkLock) {
             mPauseWork = pauseWork;
diff --git a/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/RecyclingBitmapDrawable.java b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/RecyclingBitmapDrawable.java
new file mode 100644
index 0000000..2aae97f
--- /dev/null
+++ b/samples/training/bitmapfun/src/com/example/android/bitmapfun/util/RecyclingBitmapDrawable.java
@@ -0,0 +1,103 @@
+/*
+ * 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.example.android.bitmapfun.util;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.Log;
+
+import com.example.android.bitmapfun.BuildConfig;
+
+/**
+ * A BitmapDrawable that keeps track of whether it is being displayed or cached.
+ * When the drawable is no longer being displayed or cached,
+ * {@link Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
+ */
+public class RecyclingBitmapDrawable extends BitmapDrawable {
+
+    static final String LOG_TAG = "CountingBitmapDrawable";
+
+    private int mCacheRefCount = 0;
+    private int mDisplayRefCount = 0;
+
+    private boolean mHasBeenDisplayed;
+
+    public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
+        super(res, bitmap);
+    }
+
+    /**
+     * Notify the drawable that the displayed state has changed. Internally a
+     * count is kept so that the drawable knows when it is no longer being
+     * displayed.
+     *
+     * @param isDisplayed - Whether the drawable is being displayed or not
+     */
+    public void setIsDisplayed(boolean isDisplayed) {
+        synchronized (this) {
+            if (isDisplayed) {
+                mDisplayRefCount++;
+                mHasBeenDisplayed = true;
+            } else {
+                mDisplayRefCount--;
+            }
+        }
+
+        // Check to see if recycle() can be called
+        checkState();
+    }
+
+    /**
+     * Notify the drawable that the cache state has changed. Internally a count
+     * is kept so that the drawable knows when it is no longer being cached.
+     *
+     * @param isCached - Whether the drawable is being cached or not
+     */
+    public void setIsCached(boolean isCached) {
+        synchronized (this) {
+            if (isCached) {
+                mCacheRefCount++;
+            } else {
+                mCacheRefCount--;
+            }
+        }
+
+        // Check to see if recycle() can be called
+        checkState();
+    }
+
+    private synchronized void checkState() {
+        // If the drawable cache and display ref counts = 0, and this drawable
+        // has been displayed, then recycle
+        if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
+                && hasValidBitmap()) {
+            if (BuildConfig.DEBUG) {
+                Log.d(LOG_TAG, "No longer being used or cached so recycling. "
+                        + toString());
+            }
+
+            getBitmap().recycle();
+        }
+    }
+
+    private synchronized boolean hasValidBitmap() {
+        Bitmap bitmap = getBitmap();
+        return bitmap != null && !bitmap.isRecycled();
+    }
+
+}
diff --git a/samples/training/network-usage/AndroidManifest.xml b/samples/training/network-usage/AndroidManifest.xml
new file mode 100644
index 0000000..22bbd96
--- /dev/null
+++ b/samples/training/network-usage/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.networkusage"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="4"
+        android:targetSdkVersion="14" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+
+        <activity
+            android:name="com.example.android.networkusage.NetworkActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:label="SettingsActivity" android:name=".SettingsActivity">
+             <intent-filter>
+                <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+          </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/training/network-usage/README.txt b/samples/training/network-usage/README.txt
new file mode 100644
index 0000000..cc9a7a0
--- /dev/null
+++ b/samples/training/network-usage/README.txt
@@ -0,0 +1,14 @@
+README
+======
+
+This Network Usage sample app does the following:
+
+-- Downloads an XML feed from StackOverflow.com for the most recent posts tagged "android".
+
+-- Parses the XML feed, combines feed elements with HTML markup, and displays the resulting HTML in the UI.
+
+-- Lets users control their network data usage through a settings UI. Users can choose to fetch the feed
+   when any network connection is available, or only when a Wi-Fi connection is available.
+
+-- Detects when there is a change in the device's connection status and responds accordingly. For example, if
+   the device loses its network connection, the app will not attempt to download the feed.
diff --git a/samples/training/network-usage/res/drawable-hdpi/ic_launcher.png b/samples/training/network-usage/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/samples/training/network-usage/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/network-usage/res/drawable-ldpi/ic_launcher.png b/samples/training/network-usage/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/samples/training/network-usage/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/network-usage/res/drawable-mdpi/ic_launcher.png b/samples/training/network-usage/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/samples/training/network-usage/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/network-usage/res/layout/main.xml b/samples/training/network-usage/res/layout/main.xml
new file mode 100644
index 0000000..8498934
--- /dev/null
+++ b/samples/training/network-usage/res/layout/main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/webview"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+/>
+</LinearLayout>
diff --git a/samples/training/network-usage/res/menu/mainmenu.xml b/samples/training/network-usage/res/menu/mainmenu.xml
new file mode 100644
index 0000000..17d44db
--- /dev/null
+++ b/samples/training/network-usage/res/menu/mainmenu.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:id="@+id/settings"
+          android:title="@string/settings" />
+    <item android:id="@+id/refresh"
+          android:title="@string/refresh" />
+</menu>
diff --git a/samples/training/network-usage/res/values/arrays.xml b/samples/training/network-usage/res/values/arrays.xml
new file mode 100644
index 0000000..2e8b8a7
--- /dev/null
+++ b/samples/training/network-usage/res/values/arrays.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<resources>
+    <string-array name="listArray">
+        <item>Only when on Wi-Fi</item>
+        <item>On any network</item>
+    </string-array>
+    <string-array name="listValues">
+        <item>Wi-Fi</item>
+        <item>Any</item>
+    </string-array>
+</resources>
diff --git a/samples/training/network-usage/res/values/strings.xml b/samples/training/network-usage/res/values/strings.xml
new file mode 100644
index 0000000..d7c702f
--- /dev/null
+++ b/samples/training/network-usage/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<resources>
+
+    <string name="app_name">NetworkUsage</string>
+
+    <!--  Menu items -->
+    <string name="settings">Settings</string>
+    <string name="refresh">Refresh</string>
+
+    <!--  NetworkActivity -->
+    <string name="page_title">Newest StackOverflow questions tagged \'android\'</string>
+    <string name="updated">Last updated:</string>
+    <string name="lost_connection">Lost connection.</string>
+    <string name="wifi_connected">Wi-Fi reconnected.</string>
+    <string name="connection_error">Unable to load content. Check your network connection.</string>
+    <string name="xml_error">Error parsing XML.</string>
+
+</resources>
diff --git a/samples/training/network-usage/res/xml/preferences.xml b/samples/training/network-usage/res/xml/preferences.xml
new file mode 100644
index 0000000..801ba79
--- /dev/null
+++ b/samples/training/network-usage/res/xml/preferences.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListPreference
+        android:title="Download Feed"
+        android:summary="Network connectivity required to download the feed."
+        android:key="listPref"
+        android:defaultValue="Wi-Fi"
+        android:entries="@array/listArray"
+        android:entryValues="@array/listValues"
+     />
+    <CheckBoxPreference
+        android:title="Show Summaries"
+        android:defaultValue="false"
+        android:summary="Show a summary for each link."
+        android:key="summaryPref" />
+</PreferenceScreen>
diff --git a/samples/training/network-usage/src/com/example/android/networkusage/NetworkActivity.java b/samples/training/network-usage/src/com/example/android/networkusage/NetworkActivity.java
new file mode 100644
index 0000000..b7ed331
--- /dev/null
+++ b/samples/training/network-usage/src/com/example/android/networkusage/NetworkActivity.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.example.android.networkusage;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.webkit.WebView;
+import android.widget.Toast;
+
+import com.example.android.networkusage.R;
+import com.example.android.networkusage.StackOverflowXmlParser.Entry;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+
+
+/**
+ * Main Activity for the sample application.
+ *
+ * This activity does the following:
+ *
+ * o Presents a WebView screen to users. This WebView has a list of HTML links to the latest
+ *   questions tagged 'android' on stackoverflow.com.
+ *
+ * o Parses the StackOverflow XML feed using XMLPullParser.
+ *
+ * o Uses AsyncTask to download and process the XML feed.
+ *
+ * o Monitors preferences and the device's network connection to determine whether
+ *   to refresh the WebView content.
+ */
+public class NetworkActivity extends Activity {
+    public static final String WIFI = "Wi-Fi";
+    public static final String ANY = "Any";
+    private static final String URL =
+            "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";
+
+    // Whether there is a Wi-Fi connection.
+    private static boolean wifiConnected = false;
+    // Whether there is a mobile connection.
+    private static boolean mobileConnected = false;
+    // Whether the display should be refreshed.
+    public static boolean refreshDisplay = true;
+
+    // The user's current network preference setting.
+    public static String sPref = null;
+
+    // The BroadcastReceiver that tracks network connectivity changes.
+    private NetworkReceiver receiver = new NetworkReceiver();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Register BroadcastReceiver to track connection changes.
+        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        receiver = new NetworkReceiver();
+        this.registerReceiver(receiver, filter);
+    }
+
+    // Refreshes the display if the network connection and the
+    // pref settings allow it.
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // Gets the user's network preference settings
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+        // Retrieves a string value for the preferences. The second parameter
+        // is the default value to use if a preference value is not found.
+        sPref = sharedPrefs.getString("listPref", "Wi-Fi");
+
+        updateConnectedFlags();
+
+        // Only loads the page if refreshDisplay is true. Otherwise, keeps previous
+        // display. For example, if the user has set "Wi-Fi only" in prefs and the
+        // device loses its Wi-Fi connection midway through the user using the app,
+        // you don't want to refresh the display--this would force the display of
+        // an error page instead of stackoverflow.com content.
+        if (refreshDisplay) {
+            loadPage();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (receiver != null) {
+            this.unregisterReceiver(receiver);
+        }
+    }
+
+    // Checks the network connection and sets the wifiConnected and mobileConnected
+    // variables accordingly.
+    private void updateConnectedFlags() {
+        ConnectivityManager connMgr =
+                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
+        if (activeInfo != null && activeInfo.isConnected()) {
+            wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
+            mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
+        } else {
+            wifiConnected = false;
+            mobileConnected = false;
+        }
+    }
+
+    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
+    // This avoids UI lock up. To prevent network operations from
+    // causing a delay that results in a poor user experience, always perform
+    // network operations on a separate thread from the UI.
+    private void loadPage() {
+        if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
+                || ((sPref.equals(WIFI)) && (wifiConnected))) {
+            // AsyncTask subclass
+            new DownloadXmlTask().execute(URL);
+        } else {
+            showErrorPage();
+        }
+    }
+
+    // Displays an error if the app is unable to load content.
+    private void showErrorPage() {
+        setContentView(R.layout.main);
+
+        // The specified network connection is not available. Displays error message.
+        WebView myWebView = (WebView) findViewById(R.id.webview);
+        myWebView.loadData(getResources().getString(R.string.connection_error),
+                "text/html", null);
+    }
+
+    // Populates the activity's options menu.
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.mainmenu, menu);
+        return true;
+    }
+
+    // Handles the user's menu selection.
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case R.id.settings:
+                Intent settingsActivity = new Intent(getBaseContext(), SettingsActivity.class);
+                startActivity(settingsActivity);
+                return true;
+        case R.id.refresh:
+                loadPage();
+                return true;
+        default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    // Implementation of AsyncTask used to download XML feed from stackoverflow.com.
+    private class DownloadXmlTask extends AsyncTask<String, Void, String> {
+
+        @Override
+        protected String doInBackground(String... urls) {
+            try {
+                return loadXmlFromNetwork(urls[0]);
+            } catch (IOException e) {
+                return getResources().getString(R.string.connection_error);
+            } catch (XmlPullParserException e) {
+                return getResources().getString(R.string.xml_error);
+            }
+        }
+
+        @Override
+        protected void onPostExecute(String result) {
+            setContentView(R.layout.main);
+            // Displays the HTML string in the UI via a WebView
+            WebView myWebView = (WebView) findViewById(R.id.webview);
+            myWebView.loadData(result, "text/html", null);
+        }
+    }
+
+    // Uploads XML from stackoverflow.com, parses it, and combines it with
+    // HTML markup. Returns HTML string.
+    private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException {
+        InputStream stream = null;
+        StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser();
+        List<Entry> entries = null;
+        String title = null;
+        String url = null;
+        String summary = null;
+        Calendar rightNow = Calendar.getInstance();
+        DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa");
+
+        // Checks whether the user set the preference to include summary text
+        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        boolean pref = sharedPrefs.getBoolean("summaryPref", false);
+
+        StringBuilder htmlString = new StringBuilder();
+        htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>");
+        htmlString.append("<em>" + getResources().getString(R.string.updated) + " " +
+                formatter.format(rightNow.getTime()) + "</em>");
+
+        try {
+            stream = downloadUrl(urlString);
+            entries = stackOverflowXmlParser.parse(stream);
+        // Makes sure that the InputStream is closed after the app is
+        // finished using it.
+        } finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+
+        // StackOverflowXmlParser returns a List (called "entries") of Entry objects.
+        // Each Entry object represents a single post in the XML feed.
+        // This section processes the entries list to combine each entry with HTML markup.
+        // Each entry is displayed in the UI as a link that optionally includes
+        // a text summary.
+        for (Entry entry : entries) {
+            htmlString.append("<p><a href='");
+            htmlString.append(entry.link);
+            htmlString.append("'>" + entry.title + "</a></p>");
+            // If the user set the preference to include summary text,
+            // adds it to the display.
+            if (pref) {
+                htmlString.append(entry.summary);
+            }
+        }
+        return htmlString.toString();
+    }
+
+    // Given a string representation of a URL, sets up a connection and gets
+    // an input stream.
+    private InputStream downloadUrl(String urlString) throws IOException {
+        URL url = new URL(urlString);
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setReadTimeout(10000 /* milliseconds */);
+        conn.setConnectTimeout(15000 /* milliseconds */);
+        conn.setRequestMethod("GET");
+        conn.setDoInput(true);
+        // Starts the query
+        conn.connect();
+        InputStream stream = conn.getInputStream();
+        return stream;
+    }
+
+    /**
+     *
+     * This BroadcastReceiver intercepts the android.net.ConnectivityManager.CONNECTIVITY_ACTION,
+     * which indicates a connection change. It checks whether the type is TYPE_WIFI.
+     * If it is, it checks whether Wi-Fi is connected and sets the wifiConnected flag in the
+     * main activity accordingly.
+     *
+     */
+    public class NetworkReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ConnectivityManager connMgr =
+                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+
+            // Checks the user prefs and the network connection. Based on the result, decides
+            // whether
+            // to refresh the display or keep the current display.
+            // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
+            if (WIFI.equals(sPref) && networkInfo != null
+                    && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+                // If device has its Wi-Fi connection, sets refreshDisplay
+                // to true. This causes the display to be refreshed when the user
+                // returns to the app.
+                refreshDisplay = true;
+                Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();
+
+                // If the setting is ANY network and there is a network connection
+                // (which by process of elimination would be mobile), sets refreshDisplay to true.
+            } else if (ANY.equals(sPref) && networkInfo != null) {
+                refreshDisplay = true;
+
+                // Otherwise, the app can't download content--either because there is no network
+                // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
+                // is no Wi-Fi connection.
+                // Sets refreshDisplay to false.
+            } else {
+                refreshDisplay = false;
+                Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+}
diff --git a/samples/training/network-usage/src/com/example/android/networkusage/SettingsActivity.java b/samples/training/network-usage/src/com/example/android/networkusage/SettingsActivity.java
new file mode 100644
index 0000000..73b72d2
--- /dev/null
+++ b/samples/training/network-usage/src/com/example/android/networkusage/SettingsActivity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.example.android.networkusage;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import com.example.android.networkusage.R;
+
+/**
+ * This preference activity has in its manifest declaration an intent filter for
+ * the ACTION_MANAGE_NETWORK_USAGE action. This activity provides a settings UI
+ * for users to specify network settings to control data usage.
+ */
+public class SettingsActivity extends PreferenceActivity
+        implements
+            OnSharedPreferenceChangeListener {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Loads the XML preferences file.
+        addPreferencesFromResource(R.xml.preferences);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Registers a callback to be invoked whenever a user changes a preference.
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        // Unregisters the listener set in onResume().
+        // It's best practice to unregister listeners when your app isn't using them to cut down on
+        // unnecessary system overhead. You do this in onPause().
+        getPreferenceScreen()
+                .getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+    }
+
+    // Fires when the user changes a preference.
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        // Sets refreshDisplay to true so that when the user returns to the main
+        // activity, the display refreshes to reflect the new settings.
+        NetworkActivity.refreshDisplay = true;
+    }
+}
diff --git a/samples/training/network-usage/src/com/example/android/networkusage/StackOverflowXmlParser.java b/samples/training/network-usage/src/com/example/android/networkusage/StackOverflowXmlParser.java
new file mode 100644
index 0000000..6a01098
--- /dev/null
+++ b/samples/training/network-usage/src/com/example/android/networkusage/StackOverflowXmlParser.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.example.android.networkusage;
+
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class parses XML feeds from stackoverflow.com.
+ * Given an InputStream representation of a feed, it returns a List of entries,
+ * where each list element represents a single entry (post) in the XML feed.
+ */
+public class StackOverflowXmlParser {
+    private static final String ns = null;
+
+    // We don't use namespaces
+
+    public List<Entry> parse(InputStream in) throws XmlPullParserException, IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+            parser.setInput(in, null);
+            parser.nextTag();
+            return readFeed(parser);
+        } finally {
+            in.close();
+        }
+    }
+
+    private List<Entry> readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
+        List<Entry> entries = new ArrayList<Entry>();
+
+        parser.require(XmlPullParser.START_TAG, ns, "feed");
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            String name = parser.getName();
+            // Starts by looking for the entry tag
+            if (name.equals("entry")) {
+                entries.add(readEntry(parser));
+            } else {
+                skip(parser);
+            }
+        }
+        return entries;
+    }
+
+    // This class represents a single entry (post) in the XML feed.
+    // It includes the data members "title," "link," and "summary."
+    public static class Entry {
+        public final String title;
+        public final String link;
+        public final String summary;
+
+        private Entry(String title, String summary, String link) {
+            this.title = title;
+            this.summary = summary;
+            this.link = link;
+        }
+    }
+
+    // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them
+    // off
+    // to their respective &quot;read&quot; methods for processing. Otherwise, skips the tag.
+    private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
+        parser.require(XmlPullParser.START_TAG, ns, "entry");
+        String title = null;
+        String summary = null;
+        String link = null;
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
+            }
+            String name = parser.getName();
+            if (name.equals("title")) {
+                title = readTitle(parser);
+            } else if (name.equals("summary")) {
+                summary = readSummary(parser);
+            } else if (name.equals("link")) {
+                link = readLink(parser);
+            } else {
+                skip(parser);
+            }
+        }
+        return new Entry(title, summary, link);
+    }
+
+    // Processes title tags in the feed.
+    private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {
+        parser.require(XmlPullParser.START_TAG, ns, "title");
+        String title = readText(parser);
+        parser.require(XmlPullParser.END_TAG, ns, "title");
+        return title;
+    }
+
+    // Processes link tags in the feed.
+    private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {
+        String link = "";
+        parser.require(XmlPullParser.START_TAG, ns, "link");
+        String tag = parser.getName();
+        String relType = parser.getAttributeValue(null, "rel");
+        if (tag.equals("link")) {
+            if (relType.equals("alternate")) {
+                link = parser.getAttributeValue(null, "href");
+                parser.nextTag();
+            }
+        }
+        parser.require(XmlPullParser.END_TAG, ns, "link");
+        return link;
+    }
+
+    // Processes summary tags in the feed.
+    private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {
+        parser.require(XmlPullParser.START_TAG, ns, "summary");
+        String summary = readText(parser);
+        parser.require(XmlPullParser.END_TAG, ns, "summary");
+        return summary;
+    }
+
+    // For the tags title and summary, extracts their text values.
+    private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
+        String result = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            result = parser.getText();
+            parser.nextTag();
+        }
+        return result;
+    }
+
+    // Skips tags the parser isn't interested in. Uses depth to handle nested tags. i.e.,
+    // if the next tag after a START_TAG isn't a matching END_TAG, it keeps going until it
+    // finds the matching END_TAG (as indicated by the value of "depth" being 0).
+    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            throw new IllegalStateException();
+        }
+        int depth = 1;
+        while (depth != 0) {
+            switch (parser.next()) {
+            case XmlPullParser.END_TAG:
+                    depth--;
+                    break;
+            case XmlPullParser.START_TAG:
+                    depth++;
+                    break;
+            }
+        }
+    }
+}
diff --git a/scripts/app_engine_server/memcache_zipserve.py b/scripts/app_engine_server/memcache_zipserve.py
index 75d1b97..68aa0dd 100644
--- a/scripts/app_engine_server/memcache_zipserve.py
+++ b/scripts/app_engine_server/memcache_zipserve.py
@@ -110,7 +110,7 @@
   REDIRECT_TYPE_TEMP = 'temporary'  # Redirect 'type'string indicate a 302
                                     #   Redirect should be served
   intlString = 'intl/'
-  validLangs = ['en', 'de', 'es', 'fr','it','ja','zh-CN','zh-TW']
+  validLangs = ['en', 'de', 'es', 'fr','it','ja','ko','ru','zh-CN','zh-TW']
 
   def TrueGet(self, reqUri):
     """The top-level entry point to serving requests.
diff --git a/scripts/app_engine_server/redirects.yaml b/scripts/app_engine_server/redirects.yaml
index c898403..b7c28f2 100644
--- a/scripts/app_engine_server/redirects.yaml
+++ b/scripts/app_engine_server/redirects.yaml
@@ -123,13 +123,38 @@
 
 # new one
 - src: /guide/market/
-  dst: /guide/google/play/
+  dst: /google/play/
   type: permanent
   comment: redirect billing to new loc
 
-# new one
+- src: /guide/google/gcm/client-javadoc/.*
+  dst: /reference/com/google/android/gcm/package-summary.html
+  type: permanent
+  comment: redirect to new loc
+
+- src: /guide/google/gcm/server-javadoc/.*
+  dst: /reference/com/google/android/gcm/server/package-summary.html
+  type: permanent
+  comment: redirect to new loc
+
+- src: /guide/google/play/services.html
+  dst: /google/play-services/index.html
+  type: permanent
+  comment: redirect to new loc
+
+- src: /guide/google/
+  dst: /google/
+  type: permanent
+  comment: redirect to new loc
+
 - src: /guide/publishing/licensing.html
-  dst: /guide/google/play/licensing/index.html
+  dst: /google/play/licensing/index.html
+  type: permanent
+  comment: Redirect Licensing docs to new location
+
+# new one
+- src: /google/play/billing/billing_about.html
+  dst: /google/play/billing/index.html
   type: permanent
   comment: Redirect Licensing docs to new location
 
@@ -146,10 +171,9 @@
 - src: /tools/aidl.html
   dst: /guide/components/aidl.html
   type: permanent
-  comment: bug 6755311, aidl doc found by search under tools
 
 - src: /guide/market/publishing/multiple-apks.html
-  dst: /guide/google/play/publishing/multiple-apks.html
+  dst: /google/play/publishing/multiple-apks.html
   type: permanent
   comment: Redirect to new location
 
@@ -235,13 +259,13 @@
   comment: Redirect to new location
 
 - src: /guide/topics/security/security.html
-  dst: /guide/topics/security/permissions.html
+  dst: /training/articles/security-tips.html
   type: permanent
   comment: Redirect to new location
 
 # new one
 - src: /guide/appendix/market-filters.html
-  dst: /guide/google/play/filters.html
+  dst: /google/play/filters.html
   type: permanent
   comment: Redirect to new location
 
@@ -259,6 +283,11 @@
   type: permanent
   comment: Redirect to new location
 
+- src: /guide/topics/graphics/renderscript.html
+  dst: /guide/topics/renderscript/index.html
+  type: permanent
+  comment: Redirect to new location
+
 - src: /guide/topics/location/obtaining-user-location.html
   dst: /guide/topics/location/strategies.html
   type: permanent
@@ -321,6 +350,34 @@
   dst: /guide/topics/ui/accessibility/index.html
   type: permanent
 
+# move best practices to training
+
+- src: /guide/practices/app-design/performance.html
+  dst: /training/articles/perf-tips.html
+  type: permanent
+
+- src: /guide/practices/performance.html
+  dst: /training/articles/perf-tips.html
+  type: permanent
+
+- src: /guide/practices/app-design/responsiveness.html
+  dst: /training/articles/perf-anr.html
+  type: permanent
+
+- src: /guide/practices/responsiveness.html
+  dst: /training/articles/perf-anr.html
+  type: permanent
+
+- src: /guide/practices/security.html
+  dst: /training/articles/security-tips.html
+  type: permanent
+
+- src: /guide/practices/jni.html
+  dst: /training/articles/perf-jni.html
+  type: permanent
+
+
+
 # new one
 - src: /resources/dashboard/.*
   dst: /about/dashboards/index.html
@@ -435,7 +492,7 @@
 # ------------------- TRAINING -------------------
 
 - src: /training/cloudsync/aesync.html
-  dst: /guide/google/gcm/index.html
+  dst: /google/gcm/index.html
   type: permanent
   comment: Syncing with App Engine was removed because it's obsolete.
 
@@ -479,32 +536,34 @@
 
 # ---------- PLATFORM VERSIONS ----------------
 
-- src: /4.1
-  dst: /about/versions/android-4.1.html
+- src: /4.2
+  dst: /about/versions/android-4.2.html
   type: permanent
 
-- src: /jb
-  dst: /about/versions/jelly-bean.html
+- src: /4.1
+  dst: /about/versions/android-4.1.html
   type: permanent
 
 - src: /4.0
   dst: /about/versions/android-4.0.html
   type: permanent
 
-- src: /ics
+- src: /(j|jb|jellybean)/?$
+  dst: /about/versions/jelly-bean.html
+  type: permanent
+
+- src: /(i|ics|icecreamsandwich)/?$
   dst: /about/versions/android-4.0-highlights.html
   type: permanent
 
-- src: /hc
+- src: /(h|hc|honeycomb)/?$
   dst: /about/versions/android-3.0-highlights.html
   type: permanent
 
-- src: /gb
+- src: /(g|gb|gingerbread)/?$
   dst: /about/versions/android-2.3-highlights.html
   type: permanent
 
-
-
 # ---------- MISC -----------------
 
 - src: /%2B/?$
@@ -520,4 +579,14 @@
   dst: /about/dashboards/index.html
   type: permanent
 
+- src: /youtube
+  dst: http://www.youtube.com/user/androiddevelopers
+  type: permanent
 
+- src: /playbadge
+  dst: http://developer.android.com/distribute/googleplay/promote/badges.html
+  type: permanent
+
+- src: /deviceart
+  dst: http://developer.android.com/distribute/promote/device-art.html
+  type: permanent
diff --git a/sdk/api-versions.xml b/sdk/api-versions.xml
index b223c6b..70e6584 100644
--- a/sdk/api-versions.xml
+++ b/sdk/api-versions.xml
@@ -147,16 +147,35 @@
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
 		<field name="ACCOUNTS" />
+		<field name="AFFECTS_BATTERY" since="17" />
+		<field name="APP_INFO" since="17" />
+		<field name="AUDIO_SETTINGS" since="17" />
+		<field name="BLUETOOTH_NETWORK" since="17" />
+		<field name="BOOKMARKS" since="17" />
+		<field name="CALENDAR" since="17" />
+		<field name="CAMERA" since="17" />
 		<field name="COST_MONEY" />
 		<field name="DEVELOPMENT_TOOLS" />
+		<field name="DEVICE_ALARMS" since="17" />
+		<field name="DISPLAY" since="17" />
 		<field name="HARDWARE_CONTROLS" />
 		<field name="LOCATION" />
 		<field name="MESSAGES" />
+		<field name="MICROPHONE" since="17" />
 		<field name="NETWORK" />
 		<field name="PERSONAL_INFO" />
 		<field name="PHONE_CALLS" />
+		<field name="SCREENLOCK" since="17" />
+		<field name="SOCIAL_INFO" since="17" />
+		<field name="STATUS_BAR" since="17" />
 		<field name="STORAGE" since="4" />
+		<field name="SYNC_SETTINGS" since="17" />
+		<field name="SYSTEM_CLOCK" since="17" />
 		<field name="SYSTEM_TOOLS" />
+		<field name="USER_DICTIONARY" since="17" />
+		<field name="VOICEMAIL" since="17" />
+		<field name="WALLPAPER" since="17" />
+		<field name="WRITE_USER_DICTIONARY" since="17" />
 	</class>
 	<class name="android/R" since="1">
 		<extends name="java/lang/Object" />
@@ -318,6 +337,7 @@
 		<field name="checkboxStyle" />
 		<field name="checked" />
 		<field name="checkedButton" />
+		<field name="checkedTextViewStyle" since="17" />
 		<field name="childDivider" />
 		<field name="childIndicator" />
 		<field name="childIndicatorLeft" />
@@ -477,6 +497,8 @@
 		<field name="foreground" />
 		<field name="foregroundGravity" />
 		<field name="format" />
+		<field name="format12Hour" since="17" />
+		<field name="format24Hour" since="17" />
 		<field name="fragment" since="11" />
 		<field name="fragmentCloseEnterAnimation" since="11" />
 		<field name="fragmentCloseExitAnimation" since="11" />
@@ -557,6 +579,7 @@
 		<field name="indicatorRight" />
 		<field name="inflatedId" />
 		<field name="initOrder" />
+		<field name="initialKeyguardLayout" since="17" />
 		<field name="initialLayout" since="3" />
 		<field name="innerRadius" since="3" />
 		<field name="innerRadiusRatio" />
@@ -600,6 +623,7 @@
 		<field name="keycode" />
 		<field name="killAfterRestore" since="5" />
 		<field name="label" />
+		<field name="labelFor" since="17" />
 		<field name="labelTextSize" since="3" />
 		<field name="largeHeap" since="11" />
 		<field name="largeScreens" since="4" />
@@ -608,15 +632,20 @@
 		<field name="layerType" since="11" />
 		<field name="layout" />
 		<field name="layoutAnimation" />
+		<field name="layoutDirection" since="17" />
 		<field name="layout_above" />
 		<field name="layout_alignBaseline" />
 		<field name="layout_alignBottom" />
+		<field name="layout_alignEnd" since="17" />
 		<field name="layout_alignLeft" />
 		<field name="layout_alignParentBottom" />
+		<field name="layout_alignParentEnd" since="17" />
 		<field name="layout_alignParentLeft" />
 		<field name="layout_alignParentRight" />
+		<field name="layout_alignParentStart" since="17" />
 		<field name="layout_alignParentTop" />
 		<field name="layout_alignRight" />
+		<field name="layout_alignStart" since="17" />
 		<field name="layout_alignTop" />
 		<field name="layout_alignWithParentIfMissing" />
 		<field name="layout_below" />
@@ -629,15 +658,19 @@
 		<field name="layout_height" />
 		<field name="layout_margin" />
 		<field name="layout_marginBottom" />
+		<field name="layout_marginEnd" since="17" />
 		<field name="layout_marginLeft" />
 		<field name="layout_marginRight" />
+		<field name="layout_marginStart" since="17" />
 		<field name="layout_marginTop" />
 		<field name="layout_row" since="14" />
 		<field name="layout_rowSpan" since="14" />
 		<field name="layout_scale" />
 		<field name="layout_span" />
+		<field name="layout_toEndOf" since="17" />
 		<field name="layout_toLeftOf" />
 		<field name="layout_toRightOf" />
+		<field name="layout_toStartOf" since="17" />
 		<field name="layout_weight" />
 		<field name="layout_width" />
 		<field name="layout_x" />
@@ -656,8 +689,10 @@
 		<field name="listPreferredItemHeight" />
 		<field name="listPreferredItemHeightLarge" since="14" />
 		<field name="listPreferredItemHeightSmall" since="14" />
+		<field name="listPreferredItemPaddingEnd" since="17" />
 		<field name="listPreferredItemPaddingLeft" since="14" />
 		<field name="listPreferredItemPaddingRight" since="14" />
+		<field name="listPreferredItemPaddingStart" since="17" />
 		<field name="listSelector" />
 		<field name="listSeparatorTextViewStyle" />
 		<field name="listViewStyle" />
@@ -728,8 +763,10 @@
 		<field name="packageNames" since="14" />
 		<field name="padding" />
 		<field name="paddingBottom" />
+		<field name="paddingEnd" since="17" />
 		<field name="paddingLeft" />
 		<field name="paddingRight" />
+		<field name="paddingStart" since="17" />
 		<field name="paddingTop" />
 		<field name="panelBackground" />
 		<field name="panelColorBackground" />
@@ -742,7 +779,9 @@
 		<field name="pathPattern" />
 		<field name="pathPrefix" />
 		<field name="permission" />
+		<field name="permissionFlags" since="17" />
 		<field name="permissionGroup" />
+		<field name="permissionGroupFlags" since="17" />
 		<field name="persistent" />
 		<field name="persistentDrawingCache" />
 		<field name="phoneNumber" />
@@ -762,6 +801,7 @@
 		<field name="preferenceLayoutChild" />
 		<field name="preferenceScreenStyle" />
 		<field name="preferenceStyle" />
+		<field name="presentationTheme" since="17" />
 		<field name="previewImage" since="11" />
 		<field name="priority" />
 		<field name="privateImeOptions" since="3" />
@@ -880,11 +920,13 @@
 		<field name="showAsAction" since="11" />
 		<field name="showDefault" />
 		<field name="showDividers" since="11" />
+		<field name="showOnLockScreen" since="17" />
 		<field name="showSilent" />
 		<field name="showWeekNumber" since="11" />
 		<field name="shownWeekCount" since="11" />
 		<field name="shrinkColumns" />
 		<field name="singleLine" />
+		<field name="singleUser" since="17" />
 		<field name="smallIcon" since="5" />
 		<field name="smallScreens" since="4" />
 		<field name="smoothScrollbar" since="3" />
@@ -934,6 +976,7 @@
 		<field name="subtitle" since="11" />
 		<field name="subtitleTextStyle" since="11" />
 		<field name="subtypeExtraValue" since="14" />
+		<field name="subtypeId" since="17" />
 		<field name="subtypeLocale" since="14" />
 		<field name="suggestActionMsg" />
 		<field name="suggestActionMsgColumn" />
@@ -941,6 +984,7 @@
 		<field name="summaryColumn" since="5" />
 		<field name="summaryOff" />
 		<field name="summaryOn" />
+		<field name="supportsRtl" since="17" />
 		<field name="supportsUploading" since="5" />
 		<field name="switchMinWidth" since="14" />
 		<field name="switchPadding" since="14" />
@@ -971,6 +1015,7 @@
 		<field name="tension" since="4" />
 		<field name="testOnly" since="4" />
 		<field name="text" />
+		<field name="textAlignment" since="17" />
 		<field name="textAllCaps" since="14" />
 		<field name="textAppearance" />
 		<field name="textAppearanceButton" />
@@ -1010,6 +1055,7 @@
 		<field name="textColorTertiary" />
 		<field name="textColorTertiaryInverse" />
 		<field name="textCursorDrawable" since="12" />
+		<field name="textDirection" since="17" />
 		<field name="textEditNoPasteWindowLayout" since="11" />
 		<field name="textEditPasteWindowLayout" since="11" />
 		<field name="textEditSideNoPasteWindowLayout" since="11" />
@@ -1036,6 +1082,7 @@
 		<field name="thumbTextPadding" since="14" />
 		<field name="thumbnail" since="5" />
 		<field name="tileMode" />
+		<field name="timeZone" since="17" />
 		<field name="tint" />
 		<field name="title" />
 		<field name="titleCondensed" />
@@ -1103,6 +1150,7 @@
 		<field name="weekNumberColor" since="11" />
 		<field name="weekSeparatorLineColor" since="11" />
 		<field name="weightSum" />
+		<field name="widgetCategory" since="17" />
 		<field name="widgetLayout" />
 		<field name="width" />
 		<field name="windowActionBar" since="11" />
@@ -1751,6 +1799,7 @@
 		<field name="Widget_DeviceDefault_Button_Small" since="14" />
 		<field name="Widget_DeviceDefault_Button_Toggle" since="14" />
 		<field name="Widget_DeviceDefault_CalendarView" since="14" />
+		<field name="Widget_DeviceDefault_CheckedTextView" since="17" />
 		<field name="Widget_DeviceDefault_CompoundButton_CheckBox" since="14" />
 		<field name="Widget_DeviceDefault_CompoundButton_RadioButton" since="14" />
 		<field name="Widget_DeviceDefault_CompoundButton_Star" since="14" />
@@ -1784,6 +1833,7 @@
 		<field name="Widget_DeviceDefault_Light_Button_Small" since="14" />
 		<field name="Widget_DeviceDefault_Light_Button_Toggle" since="14" />
 		<field name="Widget_DeviceDefault_Light_CalendarView" since="14" />
+		<field name="Widget_DeviceDefault_Light_CheckedTextView" since="17" />
 		<field name="Widget_DeviceDefault_Light_CompoundButton_CheckBox" since="14" />
 		<field name="Widget_DeviceDefault_Light_CompoundButton_RadioButton" since="14" />
 		<field name="Widget_DeviceDefault_Light_CompoundButton_Star" since="14" />
@@ -1869,6 +1919,7 @@
 		<field name="Widget_Holo_Button_Small" since="11" />
 		<field name="Widget_Holo_Button_Toggle" since="11" />
 		<field name="Widget_Holo_CalendarView" since="11" />
+		<field name="Widget_Holo_CheckedTextView" since="17" />
 		<field name="Widget_Holo_CompoundButton_CheckBox" since="11" />
 		<field name="Widget_Holo_CompoundButton_RadioButton" since="11" />
 		<field name="Widget_Holo_CompoundButton_Star" since="11" />
@@ -1902,6 +1953,7 @@
 		<field name="Widget_Holo_Light_Button_Small" since="11" />
 		<field name="Widget_Holo_Light_Button_Toggle" since="11" />
 		<field name="Widget_Holo_Light_CalendarView" since="11" />
+		<field name="Widget_Holo_Light_CheckedTextView" since="17" />
 		<field name="Widget_Holo_Light_CompoundButton_CheckBox" since="11" />
 		<field name="Widget_Holo_Light_CompoundButton_RadioButton" since="11" />
 		<field name="Widget_Holo_Light_CompoundButton_Star" since="11" />
@@ -2023,6 +2075,7 @@
 		<field name="GLOBAL_ACTION_BACK" since="16" />
 		<field name="GLOBAL_ACTION_HOME" since="16" />
 		<field name="GLOBAL_ACTION_NOTIFICATIONS" since="16" />
+		<field name="GLOBAL_ACTION_QUICK_SETTINGS" since="17" />
 		<field name="GLOBAL_ACTION_RECENTS" since="16" />
 		<field name="SERVICE_INTERFACE" />
 		<field name="SERVICE_META_DATA" since="14" />
@@ -2043,6 +2096,7 @@
 		<field name="DEFAULT" />
 		<field name="FEEDBACK_ALL_MASK" since="14" />
 		<field name="FEEDBACK_AUDIBLE" />
+		<field name="FEEDBACK_BRAILLE" since="17" />
 		<field name="FEEDBACK_GENERIC" />
 		<field name="FEEDBACK_HAPTIC" />
 		<field name="FEEDBACK_SPOKEN" />
@@ -2613,6 +2667,7 @@
 		<method name="invalidateOptionsMenu()V" since="11" />
 		<method name="isChangingConfigurations()Z" since="11" />
 		<method name="isChild()Z" />
+		<method name="isDestroyed()Z" since="17" />
 		<method name="isFinishing()Z" />
 		<method name="isTaskRoot()Z" />
 		<method name="managedQuery(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;" />
@@ -2952,6 +3007,7 @@
 		<method name="setNeutralButton(ILandroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;" />
 		<method name="setNeutralButton(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;" />
 		<method name="setOnCancelListener(Landroid/content/DialogInterface$OnCancelListener;)Landroid/app/AlertDialog$Builder;" />
+		<method name="setOnDismissListener(Landroid/content/DialogInterface$OnDismissListener;)Landroid/app/AlertDialog$Builder;" since="17" />
 		<method name="setOnItemSelectedListener(Landroid/widget/AdapterView$OnItemSelectedListener;)Landroid/app/AlertDialog$Builder;" />
 		<method name="setOnKeyListener(Landroid/content/DialogInterface$OnKeyListener;)Landroid/app/AlertDialog$Builder;" />
 		<method name="setPositiveButton(ILandroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;" />
@@ -3262,9 +3318,11 @@
 		<method name="dump(Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V" />
 		<method name="getActivity()Landroid/app/Activity;" />
 		<method name="getArguments()Landroid/os/Bundle;" />
+		<method name="getChildFragmentManager()Landroid/app/FragmentManager;" since="17" />
 		<method name="getFragmentManager()Landroid/app/FragmentManager;" />
 		<method name="getId()I" />
 		<method name="getLoaderManager()Landroid/app/LoaderManager;" />
+		<method name="getParentFragment()Landroid/app/Fragment;" since="17" />
 		<method name="getResources()Landroid/content/res/Resources;" />
 		<method name="getRetainInstance()Z" />
 		<method name="getString(I)Ljava/lang/String;" />
@@ -3308,6 +3366,7 @@
 		<method name="onStart()V" />
 		<method name="onStop()V" />
 		<method name="onViewCreated(Landroid/view/View;Landroid/os/Bundle;)V" since="13" />
+		<method name="onViewStateRestored(Landroid/os/Bundle;)V" since="17" />
 		<method name="registerForContextMenu(Landroid/view/View;)V" />
 		<method name="setArguments(Landroid/os/Bundle;)V" />
 		<method name="setHasOptionsMenu(Z)V" />
@@ -3362,6 +3421,7 @@
 		<method name="getBackStackEntryCount()I" />
 		<method name="getFragment(Landroid/os/Bundle;Ljava/lang/String;)Landroid/app/Fragment;" />
 		<method name="invalidateOptionsMenu()V" since="14" />
+		<method name="isDestroyed()Z" since="17" />
 		<method name="popBackStack()V" />
 		<method name="popBackStack(II)V" />
 		<method name="popBackStack(Ljava/lang/String;I)V" />
@@ -3722,6 +3782,7 @@
 		<method name="setOnlyAlertOnce(Z)Landroid/app/Notification$Builder;" />
 		<method name="setPriority(I)Landroid/app/Notification$Builder;" since="16" />
 		<method name="setProgress(IIZ)Landroid/app/Notification$Builder;" since="14" />
+		<method name="setShowWhen(Z)Landroid/app/Notification$Builder;" since="17" />
 		<method name="setSmallIcon(I)Landroid/app/Notification$Builder;" />
 		<method name="setSmallIcon(II)Landroid/app/Notification$Builder;" />
 		<method name="setSound(Landroid/net/Uri;)Landroid/app/Notification$Builder;" />
@@ -3772,6 +3833,9 @@
 		<method name="getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" />
 		<method name="getActivity(Landroid/content/Context;ILandroid/content/Intent;ILandroid/os/Bundle;)Landroid/app/PendingIntent;" since="16" />
 		<method name="getBroadcast(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" />
+		<method name="getCreatorPackage()Ljava/lang/String;" since="17" />
+		<method name="getCreatorUid()I" since="17" />
+		<method name="getCreatorUserHandle()Landroid/os/UserHandle;" since="17" />
 		<method name="getIntentSender()Landroid/content/IntentSender;" since="4" />
 		<method name="getService(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" />
 		<method name="getTargetPackage()Ljava/lang/String;" />
@@ -3799,6 +3863,15 @@
 		<extends name="java/lang/Object" />
 		<method name="onSendFinished(Landroid/app/PendingIntent;Landroid/content/Intent;ILjava/lang/String;Landroid/os/Bundle;)V" />
 	</class>
+	<class name="android/app/Presentation" since="17">
+		<extends name="android/app/Dialog" />
+		<method name="&lt;init>(Landroid/content/Context;Landroid/view/Display;)V" />
+		<method name="&lt;init>(Landroid/content/Context;Landroid/view/Display;I)V" />
+		<method name="getDisplay()Landroid/view/Display;" />
+		<method name="getResources()Landroid/content/res/Resources;" />
+		<method name="onDisplayChanged()V" />
+		<method name="onDisplayRemoved()V" />
+	</class>
 	<class name="android/app/ProgressDialog" since="1">
 		<extends name="android/app/AlertDialog" />
 		<method name="&lt;init>(Landroid/content/Context;)V" />
@@ -4030,6 +4103,7 @@
 		<method name="getFastDrawable()Landroid/graphics/drawable/Drawable;" />
 		<method name="getInstance(Landroid/content/Context;)Landroid/app/WallpaperManager;" />
 		<method name="getWallpaperInfo()Landroid/app/WallpaperInfo;" since="7" />
+		<method name="hasResourceWallpaper(I)Z" since="17" />
 		<method name="peekDrawable()Landroid/graphics/drawable/Drawable;" />
 		<method name="peekFastDrawable()Landroid/graphics/drawable/Drawable;" />
 		<method name="sendWallpaperCommand(Landroid/os/IBinder;Ljava/lang/String;IIILandroid/os/Bundle;)V" since="7" />
@@ -4065,6 +4139,7 @@
 		<field name="CREATOR" />
 		<field name="USES_ENCRYPTED_STORAGE" since="11" />
 		<field name="USES_POLICY_DISABLE_CAMERA" since="14" />
+		<field name="USES_POLICY_DISABLE_KEYGUARD_FEATURES" since="17" />
 		<field name="USES_POLICY_EXPIRE_PASSWORD" since="11" />
 		<field name="USES_POLICY_FORCE_LOCK" />
 		<field name="USES_POLICY_LIMIT_PASSWORD" />
@@ -4100,6 +4175,7 @@
 		<method name="getActiveAdmins()Ljava/util/List;" />
 		<method name="getCameraDisabled(Landroid/content/ComponentName;)Z" since="14" />
 		<method name="getCurrentFailedPasswordAttempts()I" />
+		<method name="getKeyguardDisabledFeatures(Landroid/content/ComponentName;)I" since="17" />
 		<method name="getMaximumFailedPasswordsForWipe(Landroid/content/ComponentName;)I" />
 		<method name="getMaximumTimeToLock(Landroid/content/ComponentName;)J" />
 		<method name="getPasswordExpiration(Landroid/content/ComponentName;)J" since="11" />
@@ -4123,6 +4199,7 @@
 		<method name="removeActiveAdmin(Landroid/content/ComponentName;)V" />
 		<method name="resetPassword(Ljava/lang/String;I)Z" />
 		<method name="setCameraDisabled(Landroid/content/ComponentName;Z)V" since="14" />
+		<method name="setKeyguardDisabledFeatures(Landroid/content/ComponentName;I)V" since="17" />
 		<method name="setMaximumFailedPasswordsForWipe(Landroid/content/ComponentName;I)V" />
 		<method name="setMaximumTimeToLock(Landroid/content/ComponentName;J)V" />
 		<method name="setPasswordExpirationTimeout(Landroid/content/ComponentName;J)V" since="11" />
@@ -4146,6 +4223,10 @@
 		<field name="ENCRYPTION_STATUS_UNSUPPORTED" since="11" />
 		<field name="EXTRA_ADD_EXPLANATION" />
 		<field name="EXTRA_DEVICE_ADMIN" />
+		<field name="KEYGUARD_DISABLE_FEATURES_ALL" since="17" />
+		<field name="KEYGUARD_DISABLE_FEATURES_NONE" since="17" />
+		<field name="KEYGUARD_DISABLE_SECURE_CAMERA" since="17" />
+		<field name="KEYGUARD_DISABLE_WIDGETS_ALL" since="17" />
 		<field name="PASSWORD_QUALITY_ALPHABETIC" />
 		<field name="PASSWORD_QUALITY_ALPHANUMERIC" />
 		<field name="PASSWORD_QUALITY_BIOMETRIC_WEAK" since="14" />
@@ -4245,6 +4326,7 @@
 		<method name="deleteHost()V" />
 		<method name="onCreateView(Landroid/content/Context;ILandroid/appwidget/AppWidgetProviderInfo;)Landroid/appwidget/AppWidgetHostView;" />
 		<method name="onProviderChanged(ILandroid/appwidget/AppWidgetProviderInfo;)V" />
+		<method name="onProvidersChanged()V" since="17" />
 		<method name="startListening()V" />
 		<method name="stopListening()V" />
 	</class>
@@ -4268,6 +4350,7 @@
 		<method name="&lt;init>()V" />
 		<method name="bindAppWidgetId(ILandroid/content/ComponentName;)V" />
 		<method name="bindAppWidgetIdIfAllowed(ILandroid/content/ComponentName;)Z" since="16" />
+		<method name="bindAppWidgetIdIfAllowed(ILandroid/content/ComponentName;Landroid/os/Bundle;)Z" since="17" />
 		<method name="getAppWidgetIds(Landroid/content/ComponentName;)[I" />
 		<method name="getAppWidgetInfo(I)Landroid/appwidget/AppWidgetProviderInfo;" />
 		<method name="getAppWidgetOptions(I)Landroid/os/Bundle;" since="16" />
@@ -4297,6 +4380,7 @@
 		<field name="EXTRA_CUSTOM_INFO" />
 		<field name="INVALID_APPWIDGET_ID" />
 		<field name="META_DATA_APPWIDGET_PROVIDER" />
+		<field name="OPTION_APPWIDGET_HOST_CATEGORY" since="17" />
 		<field name="OPTION_APPWIDGET_MAX_HEIGHT" since="16" />
 		<field name="OPTION_APPWIDGET_MAX_WIDTH" since="16" />
 		<field name="OPTION_APPWIDGET_MIN_HEIGHT" since="16" />
@@ -4316,14 +4400,18 @@
 		<implements name="android/os/Parcelable" />
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(Landroid/os/Parcel;)V" />
+		<method name="clone()Landroid/appwidget/AppWidgetProviderInfo;" since="17" />
 		<field name="CREATOR" />
 		<field name="RESIZE_BOTH" since="12" />
 		<field name="RESIZE_HORIZONTAL" since="12" />
 		<field name="RESIZE_NONE" since="12" />
 		<field name="RESIZE_VERTICAL" since="12" />
+		<field name="WIDGET_CATEGORY_HOME_SCREEN" since="17" />
+		<field name="WIDGET_CATEGORY_KEYGUARD" since="17" />
 		<field name="autoAdvanceViewId" since="11" />
 		<field name="configure" />
 		<field name="icon" />
+		<field name="initialKeyguardLayout" since="17" />
 		<field name="initialLayout" />
 		<field name="label" />
 		<field name="minHeight" />
@@ -4334,6 +4422,7 @@
 		<field name="provider" />
 		<field name="resizeMode" since="12" />
 		<field name="updatePeriodMillis" />
+		<field name="widgetCategory" since="17" />
 	</class>
 	<class name="android/bluetooth/BluetoothA2dp" since="11">
 		<extends name="java/lang/Object" />
@@ -4815,18 +4904,6 @@
 		<method name="setResultData(Ljava/lang/String;)V" />
 		<method name="setResultExtras(Landroid/os/Bundle;)V" />
 	</class>
-	<class name="android/content/CancellationSignal" since="16">
-		<extends name="java/lang/Object" />
-		<method name="&lt;init>()V" />
-		<method name="cancel()V" />
-		<method name="isCanceled()Z" />
-		<method name="setOnCancelListener(Landroid/content/CancellationSignal$OnCancelListener;)V" />
-		<method name="throwIfCanceled()V" />
-	</class>
-	<class name="android/content/CancellationSignal$OnCancelListener" since="16">
-		<extends name="java/lang/Object" />
-		<method name="onCancel()V" />
-	</class>
 	<class name="android/content/ClipData" since="11">
 		<extends name="java/lang/Object" />
 		<implements name="android/os/Parcelable" />
@@ -4971,6 +5048,7 @@
 		<method name="&lt;init>()V" />
 		<method name="applyBatch(Ljava/util/ArrayList;)[Landroid/content/ContentProviderResult;" />
 		<method name="bulkInsert(Landroid/net/Uri;[Landroid/content/ContentValues;)I" />
+		<method name="call(Ljava/lang/String;Ljava/lang/String;Landroid/os/Bundle;)Landroid/os/Bundle;" since="17" />
 		<method name="delete(Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I" />
 		<method name="getLocalContentProvider()Landroid/content/ContentProvider;" />
 		<method name="getStreamTypes(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;" since="11" />
@@ -5156,6 +5234,8 @@
 		<method name="checkUriPermission(Landroid/net/Uri;III)I" />
 		<method name="checkUriPermission(Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;III)I" />
 		<method name="clearWallpaper()V" />
+		<method name="createConfigurationContext(Landroid/content/res/Configuration;)Landroid/content/Context;" since="17" />
+		<method name="createDisplayContext(Landroid/view/Display;)Landroid/content/Context;" since="17" />
 		<method name="createPackageContext(Ljava/lang/String;I)Landroid/content/Context;" />
 		<method name="databaseList()[Ljava/lang/String;" />
 		<method name="deleteDatabase(Ljava/lang/String;)Z" />
@@ -5211,13 +5291,19 @@
 		<method name="registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;" />
 		<method name="registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;Ljava/lang/String;Landroid/os/Handler;)Landroid/content/Intent;" />
 		<method name="removeStickyBroadcast(Landroid/content/Intent;)V" />
+		<method name="removeStickyBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;)V" since="17" />
 		<method name="revokeUriPermission(Landroid/net/Uri;I)V" />
 		<method name="sendBroadcast(Landroid/content/Intent;)V" />
 		<method name="sendBroadcast(Landroid/content/Intent;Ljava/lang/String;)V" />
+		<method name="sendBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;)V" since="17" />
+		<method name="sendBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;)V" since="17" />
 		<method name="sendOrderedBroadcast(Landroid/content/Intent;Ljava/lang/String;)V" />
 		<method name="sendOrderedBroadcast(Landroid/content/Intent;Ljava/lang/String;Landroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V" />
+		<method name="sendOrderedBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Ljava/lang/String;Landroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V" since="17" />
 		<method name="sendStickyBroadcast(Landroid/content/Intent;)V" />
+		<method name="sendStickyBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;)V" since="17" />
 		<method name="sendStickyOrderedBroadcast(Landroid/content/Intent;Landroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V" since="5" />
+		<method name="sendStickyOrderedBroadcastAsUser(Landroid/content/Intent;Landroid/os/UserHandle;Landroid/content/BroadcastReceiver;Landroid/os/Handler;ILjava/lang/String;Landroid/os/Bundle;)V" since="17" />
 		<method name="setTheme(I)V" />
 		<method name="setWallpaper(Landroid/graphics/Bitmap;)V" />
 		<method name="setWallpaper(Ljava/io/InputStream;)V" />
@@ -5252,6 +5338,7 @@
 		<field name="CONTEXT_INCLUDE_CODE" />
 		<field name="CONTEXT_RESTRICTED" since="4" />
 		<field name="DEVICE_POLICY_SERVICE" since="8" />
+		<field name="DISPLAY_SERVICE" since="17" />
 		<field name="DOWNLOAD_SERVICE" since="9" />
 		<field name="DROPBOX_SERVICE" since="8" />
 		<field name="INPUT_METHOD_SERVICE" since="3" />
@@ -5277,6 +5364,7 @@
 		<field name="TEXT_SERVICES_MANAGER_SERVICE" since="14" />
 		<field name="UI_MODE_SERVICE" since="8" />
 		<field name="USB_SERVICE" since="12" />
+		<field name="USER_SERVICE" since="17" />
 		<field name="VIBRATOR_SERVICE" />
 		<field name="WALLPAPER_SERVICE" />
 		<field name="WIFI_P2P_SERVICE" since="14" />
@@ -5516,6 +5604,8 @@
 		<field name="ACTION_DEVICE_STORAGE_OK" />
 		<field name="ACTION_DIAL" />
 		<field name="ACTION_DOCK_EVENT" since="5" />
+		<field name="ACTION_DREAMING_STARTED" since="17" />
+		<field name="ACTION_DREAMING_STOPPED" since="17" />
 		<field name="ACTION_EDIT" />
 		<field name="ACTION_EXTERNAL_APPLICATIONS_AVAILABLE" since="8" />
 		<field name="ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE" since="8" />
@@ -5557,6 +5647,7 @@
 		<field name="ACTION_PACKAGE_REMOVED" />
 		<field name="ACTION_PACKAGE_REPLACED" since="3" />
 		<field name="ACTION_PACKAGE_RESTARTED" />
+		<field name="ACTION_PACKAGE_VERIFIED" since="17" />
 		<field name="ACTION_PASTE" since="11" />
 		<field name="ACTION_PICK" />
 		<field name="ACTION_PICK_ACTIVITY" />
@@ -5564,6 +5655,7 @@
 		<field name="ACTION_POWER_DISCONNECTED" since="4" />
 		<field name="ACTION_POWER_USAGE_SUMMARY" since="4" />
 		<field name="ACTION_PROVIDER_CHANGED" />
+		<field name="ACTION_QUICK_CLOCK" since="17" />
 		<field name="ACTION_REBOOT" />
 		<field name="ACTION_RUN" />
 		<field name="ACTION_SCREEN_OFF" />
@@ -5584,6 +5676,9 @@
 		<field name="ACTION_UMS_CONNECTED" />
 		<field name="ACTION_UMS_DISCONNECTED" />
 		<field name="ACTION_UNINSTALL_PACKAGE" since="14" />
+		<field name="ACTION_USER_BACKGROUND" since="17" />
+		<field name="ACTION_USER_FOREGROUND" since="17" />
+		<field name="ACTION_USER_INITIALIZE" since="17" />
 		<field name="ACTION_USER_PRESENT" since="3" />
 		<field name="ACTION_VIEW" />
 		<field name="ACTION_VOICE_COMMAND" />
@@ -5648,7 +5743,9 @@
 		<field name="EXTRA_KEY_EVENT" />
 		<field name="EXTRA_LOCAL_ONLY" since="11" />
 		<field name="EXTRA_NOT_UNKNOWN_SOURCE" since="14" />
+		<field name="EXTRA_ORIGINATING_URI" since="17" />
 		<field name="EXTRA_PHONE_NUMBER" />
+		<field name="EXTRA_REFERRER" since="17" />
 		<field name="EXTRA_REMOTE_INTENT_TOKEN" since="5" />
 		<field name="EXTRA_REPLACING" since="3" />
 		<field name="EXTRA_RETURN_RESULT" since="14" />
@@ -5674,7 +5771,6 @@
 		<field name="FLAG_ACTIVITY_CLEAR_TASK" since="11" />
 		<field name="FLAG_ACTIVITY_CLEAR_TOP" />
 		<field name="FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET" since="3" />
-		<field name="FLAG_ACTIVITY_CLOSE_SYSTEM_DIALOGS" since="16" />
 		<field name="FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS" />
 		<field name="FLAG_ACTIVITY_FORWARD_RESULT" />
 		<field name="FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY" />
@@ -5796,6 +5892,9 @@
 		<extends name="java/lang/Object" />
 		<implements name="android/os/Parcelable" />
 		<method name="&lt;init>()V" />
+		<method name="getCreatorPackage()Ljava/lang/String;" since="17" />
+		<method name="getCreatorUid()I" since="17" />
+		<method name="getCreatorUserHandle()Landroid/os/UserHandle;" since="17" />
 		<method name="getTargetPackage()Ljava/lang/String;" since="9" />
 		<method name="readIntentSenderOrNullFromParcel(Landroid/os/Parcel;)Landroid/content/IntentSender;" />
 		<method name="sendIntent(Landroid/content/Context;ILandroid/content/Intent;Landroid/content/IntentSender$OnFinished;Landroid/os/Handler;)V" />
@@ -5871,11 +5970,6 @@
 		<method name="&lt;init>(Ljava/lang/Throwable;)V" />
 		<method name="getNumSuccessfulYieldPoints()I" />
 	</class>
-	<class name="android/content/OperationCanceledException" since="16">
-		<extends name="java/lang/RuntimeException" />
-		<method name="&lt;init>()V" />
-		<method name="&lt;init>(Ljava/lang/String;)V" />
-	</class>
 	<class name="android/content/PeriodicSync" since="8">
 		<extends name="java/lang/Object" />
 		<implements name="android/os/Parcelable" />
@@ -6020,9 +6114,11 @@
 		<method name="&lt;init>(Landroid/content/pm/ActivityInfo;)V" />
 		<method name="dump(Landroid/util/Printer;Ljava/lang/String;)V" />
 		<method name="getThemeResource()I" />
+		<field name="CONFIG_DENSITY" since="17" />
 		<field name="CONFIG_FONT_SCALE" />
 		<field name="CONFIG_KEYBOARD" />
 		<field name="CONFIG_KEYBOARD_HIDDEN" />
+		<field name="CONFIG_LAYOUT_DIRECTION" since="17" />
 		<field name="CONFIG_LOCALE" />
 		<field name="CONFIG_MCC" />
 		<field name="CONFIG_MNC" />
@@ -6043,6 +6139,7 @@
 		<field name="FLAG_HARDWARE_ACCELERATED" since="11" />
 		<field name="FLAG_MULTIPROCESS" />
 		<field name="FLAG_NO_HISTORY" since="3" />
+		<field name="FLAG_SINGLE_USER" since="17" />
 		<field name="FLAG_STATE_NOT_NEEDED" />
 		<field name="LAUNCH_MULTIPLE" />
 		<field name="LAUNCH_SINGLE_INSTANCE" />
@@ -6088,6 +6185,8 @@
 		<field name="FLAG_EXTERNAL_STORAGE" since="8" />
 		<field name="FLAG_FACTORY_TEST" />
 		<field name="FLAG_HAS_CODE" />
+		<field name="FLAG_INSTALLED" since="17" />
+		<field name="FLAG_IS_DATA_ONLY" since="17" />
 		<field name="FLAG_KILL_AFTER_RESTORE" since="8" />
 		<field name="FLAG_LARGE_HEAP" since="11" />
 		<field name="FLAG_PERSISTENT" />
@@ -6096,6 +6195,7 @@
 		<field name="FLAG_STOPPED" since="12" />
 		<field name="FLAG_SUPPORTS_LARGE_SCREENS" since="4" />
 		<field name="FLAG_SUPPORTS_NORMAL_SCREENS" since="4" />
+		<field name="FLAG_SUPPORTS_RTL" since="17" />
 		<field name="FLAG_SUPPORTS_SCREEN_DENSITIES" since="4" />
 		<field name="FLAG_SUPPORTS_SMALL_SCREENS" since="4" />
 		<field name="FLAG_SUPPORTS_XLARGE_SCREENS" since="11" />
@@ -6278,6 +6378,7 @@
 		<method name="checkSignatures(Ljava/lang/String;Ljava/lang/String;)I" />
 		<method name="clearPackagePreferredActivities(Ljava/lang/String;)V" />
 		<method name="currentToCanonicalPackageNames([Ljava/lang/String;)[Ljava/lang/String;" since="8" />
+		<method name="extendVerificationTimeout(IIJ)V" since="17" />
 		<method name="getActivityIcon(Landroid/content/ComponentName;)Landroid/graphics/drawable/Drawable;" />
 		<method name="getActivityIcon(Landroid/content/Intent;)Landroid/graphics/drawable/Drawable;" />
 		<method name="getActivityInfo(Landroid/content/ComponentName;I)Landroid/content/pm/ActivityInfo;" />
@@ -6344,9 +6445,11 @@
 		<field name="COMPONENT_ENABLED_STATE_ENABLED" />
 		<field name="DONT_KILL_APP" />
 		<field name="EXTRA_VERIFICATION_ID" since="14" />
+		<field name="EXTRA_VERIFICATION_RESULT" since="17" />
 		<field name="FEATURE_AUDIO_LOW_LATENCY" since="9" />
 		<field name="FEATURE_BLUETOOTH" since="8" />
 		<field name="FEATURE_CAMERA" since="7" />
+		<field name="FEATURE_CAMERA_ANY" since="17" />
 		<field name="FEATURE_CAMERA_AUTOFOCUS" since="7" />
 		<field name="FEATURE_CAMERA_FLASH" since="7" />
 		<field name="FEATURE_CAMERA_FRONT" since="9" />
@@ -6423,6 +6526,7 @@
 		<field name="INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION" />
 		<field name="INSTALL_SUCCEEDED" />
 		<field name="MATCH_DEFAULT_ONLY" />
+		<field name="MAXIMUM_VERIFICATION_TIMEOUT" since="17" />
 		<field name="PERMISSION_DENIED" />
 		<field name="PERMISSION_GRANTED" />
 		<field name="PKG_INSTALL_COMPLETE" />
@@ -6459,6 +6563,17 @@
 		<field name="externalObbSize" since="11" />
 		<field name="packageName" />
 	</class>
+	<class name="android/content/pm/PackageUserState" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="&lt;init>(Landroid/content/pm/PackageUserState;)V" />
+		<field name="disabledComponents" />
+		<field name="enabled" />
+		<field name="enabledComponents" />
+		<field name="installed" />
+		<field name="notLaunched" />
+		<field name="stopped" />
+	</class>
 	<class name="android/content/pm/PathPermission" since="4">
 		<extends name="android/os/PatternMatcher" />
 		<method name="&lt;init>(Landroid/os/Parcel;)V" />
@@ -6474,8 +6589,11 @@
 		<method name="&lt;init>(Landroid/content/pm/PermissionGroupInfo;)V" />
 		<method name="loadDescription(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;" />
 		<field name="CREATOR" />
+		<field name="FLAG_PERSONAL_INFO" since="17" />
 		<field name="descriptionRes" />
+		<field name="flags" since="17" />
 		<field name="nonLocalizedDescription" />
+		<field name="priority" since="17" />
 	</class>
 	<class name="android/content/pm/PermissionInfo" since="1">
 		<extends name="android/content/pm/PackageItemInfo" />
@@ -6484,6 +6602,7 @@
 		<method name="&lt;init>(Landroid/content/pm/PermissionInfo;)V" />
 		<method name="loadDescription(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;" />
 		<field name="CREATOR" />
+		<field name="FLAG_COSTS_MONEY" since="17" />
 		<field name="PROTECTION_DANGEROUS" />
 		<field name="PROTECTION_FLAG_DEVELOPMENT" since="16" />
 		<field name="PROTECTION_FLAG_SYSTEM" since="16" />
@@ -6493,6 +6612,7 @@
 		<field name="PROTECTION_SIGNATURE" />
 		<field name="PROTECTION_SIGNATURE_OR_SYSTEM" />
 		<field name="descriptionRes" />
+		<field name="flags" since="17" />
 		<field name="group" />
 		<field name="nonLocalizedDescription" />
 		<field name="protectionLevel" />
@@ -6503,7 +6623,9 @@
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(Landroid/content/pm/ProviderInfo;)V" />
 		<field name="CREATOR" />
+		<field name="FLAG_SINGLE_USER" since="17" />
 		<field name="authority" />
+		<field name="flags" since="17" />
 		<field name="grantUriPermissions" />
 		<field name="initOrder" />
 		<field name="isSyncable" />
@@ -6517,6 +6639,7 @@
 		<extends name="java/lang/Object" />
 		<implements name="android/os/Parcelable" />
 		<method name="&lt;init>()V" />
+		<method name="&lt;init>(Landroid/content/pm/ResolveInfo;)V" since="17" />
 		<method name="dump(Landroid/util/Printer;Ljava/lang/String;)V" />
 		<method name="getIconResource()I" />
 		<method name="loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;" />
@@ -6549,6 +6672,7 @@
 		<method name="dump(Landroid/util/Printer;Ljava/lang/String;)V" since="5" />
 		<field name="CREATOR" />
 		<field name="FLAG_ISOLATED_PROCESS" since="16" />
+		<field name="FLAG_SINGLE_USER" since="17" />
 		<field name="FLAG_STOP_WITH_TASK" since="14" />
 		<field name="flags" since="14" />
 		<field name="permission" />
@@ -6631,13 +6755,17 @@
 		<method name="compareTo(Landroid/content/res/Configuration;)I" />
 		<method name="diff(Landroid/content/res/Configuration;)I" />
 		<method name="equals(Landroid/content/res/Configuration;)Z" />
+		<method name="getLayoutDirection()I" since="17" />
 		<method name="isLayoutSizeAtLeast(I)Z" since="11" />
 		<method name="needNewResources(II)Z" />
 		<method name="readFromParcel(Landroid/os/Parcel;)V" since="8" />
+		<method name="setLayoutDirection(Ljava/util/Locale;)V" since="17" />
+		<method name="setLocale(Ljava/util/Locale;)V" since="17" />
 		<method name="setTo(Landroid/content/res/Configuration;)V" since="8" />
 		<method name="setToDefaults()V" />
 		<method name="updateFrom(Landroid/content/res/Configuration;)I" />
 		<field name="CREATOR" />
+		<field name="DENSITY_DPI_UNDEFINED" since="17" />
 		<field name="HARDKEYBOARDHIDDEN_NO" since="3" />
 		<field name="HARDKEYBOARDHIDDEN_UNDEFINED" since="3" />
 		<field name="HARDKEYBOARDHIDDEN_YES" since="3" />
@@ -6660,6 +6788,11 @@
 		<field name="ORIENTATION_PORTRAIT" />
 		<field name="ORIENTATION_SQUARE" />
 		<field name="ORIENTATION_UNDEFINED" />
+		<field name="SCREENLAYOUT_LAYOUTDIR_LTR" since="17" />
+		<field name="SCREENLAYOUT_LAYOUTDIR_MASK" since="17" />
+		<field name="SCREENLAYOUT_LAYOUTDIR_RTL" since="17" />
+		<field name="SCREENLAYOUT_LAYOUTDIR_SHIFT" since="17" />
+		<field name="SCREENLAYOUT_LAYOUTDIR_UNDEFINED" since="17" />
 		<field name="SCREENLAYOUT_LONG_MASK" since="4" />
 		<field name="SCREENLAYOUT_LONG_NO" since="4" />
 		<field name="SCREENLAYOUT_LONG_UNDEFINED" since="4" />
@@ -6670,6 +6803,7 @@
 		<field name="SCREENLAYOUT_SIZE_SMALL" since="4" />
 		<field name="SCREENLAYOUT_SIZE_UNDEFINED" since="4" />
 		<field name="SCREENLAYOUT_SIZE_XLARGE" since="9" />
+		<field name="SCREENLAYOUT_UNDEFINED" since="17" />
 		<field name="SCREEN_HEIGHT_DP_UNDEFINED" since="13" />
 		<field name="SCREEN_WIDTH_DP_UNDEFINED" since="13" />
 		<field name="SMALLEST_SCREEN_WIDTH_DP_UNDEFINED" since="13" />
@@ -6688,6 +6822,7 @@
 		<field name="UI_MODE_TYPE_NORMAL" since="8" />
 		<field name="UI_MODE_TYPE_TELEVISION" since="13" />
 		<field name="UI_MODE_TYPE_UNDEFINED" since="8" />
+		<field name="densityDpi" since="17" />
 		<field name="fontScale" />
 		<field name="hardKeyboardHidden" since="3" />
 		<field name="keyboard" />
@@ -7842,6 +7977,9 @@
 		<method name="createBitmap(Landroid/graphics/Bitmap;)Landroid/graphics/Bitmap;" />
 		<method name="createBitmap(Landroid/graphics/Bitmap;IIII)Landroid/graphics/Bitmap;" />
 		<method name="createBitmap(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;" />
+		<method name="createBitmap(Landroid/util/DisplayMetrics;IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;" since="17" />
+		<method name="createBitmap(Landroid/util/DisplayMetrics;[IIIIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;" since="17" />
+		<method name="createBitmap(Landroid/util/DisplayMetrics;[IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;" since="17" />
 		<method name="createBitmap([IIIIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;" />
 		<method name="createBitmap([IIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;" />
 		<method name="createScaledBitmap(Landroid/graphics/Bitmap;IIZ)Landroid/graphics/Bitmap;" />
@@ -7865,13 +8003,16 @@
 		<method name="getScaledWidth(Landroid/util/DisplayMetrics;)I" since="4" />
 		<method name="getWidth()I" />
 		<method name="hasAlpha()Z" />
+		<method name="hasMipMap()Z" since="17" />
 		<method name="isMutable()Z" />
+		<method name="isPremultiplied()Z" since="17" />
 		<method name="isRecycled()Z" />
 		<method name="prepareToDraw()V" since="4" />
 		<method name="recycle()V" />
 		<method name="sameAs(Landroid/graphics/Bitmap;)Z" since="12" />
 		<method name="setDensity(I)V" since="4" />
 		<method name="setHasAlpha(Z)V" since="12" />
+		<method name="setHasMipMap(Z)V" since="17" />
 		<method name="setPixel(III)V" />
 		<method name="setPixels([IIIIIII)V" />
 		<field name="CREATOR" />
@@ -8360,6 +8501,7 @@
 		<method name="getTextAlign()Landroid/graphics/Paint$Align;" />
 		<method name="getTextBounds(Ljava/lang/String;IILandroid/graphics/Rect;)V" />
 		<method name="getTextBounds([CIILandroid/graphics/Rect;)V" />
+		<method name="getTextLocale()Ljava/util/Locale;" since="17" />
 		<method name="getTextPath(Ljava/lang/String;IIFFLandroid/graphics/Path;)V" />
 		<method name="getTextPath([CIIFFLandroid/graphics/Path;)V" />
 		<method name="getTextScaleX()F" />
@@ -8409,6 +8551,7 @@
 		<method name="setStyle(Landroid/graphics/Paint$Style;)V" />
 		<method name="setSubpixelText(Z)V" />
 		<method name="setTextAlign(Landroid/graphics/Paint$Align;)V" />
+		<method name="setTextLocale(Ljava/util/Locale;)V" since="17" />
 		<method name="setTextScaleX(F)V" />
 		<method name="setTextSize(F)V" />
 		<method name="setTextSkewX(F)V" />
@@ -9237,6 +9380,7 @@
 		<method name="addCallbackBuffer([B)V" since="8" />
 		<method name="autoFocus(Landroid/hardware/Camera$AutoFocusCallback;)V" />
 		<method name="cancelAutoFocus()V" since="5" />
+		<method name="enableShutterSound(Z)Z" since="17" />
 		<method name="getCameraInfo(ILandroid/hardware/Camera$CameraInfo;)V" since="9" />
 		<method name="getNumberOfCameras()I" since="9" />
 		<method name="getParameters()Landroid/hardware/Camera$Parameters;" />
@@ -9289,6 +9433,7 @@
 		<method name="&lt;init>()V" />
 		<field name="CAMERA_FACING_BACK" />
 		<field name="CAMERA_FACING_FRONT" />
+		<field name="canDisableShutterSound" since="17" />
 		<field name="facing" />
 		<field name="orientation" />
 	</class>
@@ -9445,6 +9590,7 @@
 		<field name="SCENE_MODE_BEACH" since="5" />
 		<field name="SCENE_MODE_CANDLELIGHT" since="5" />
 		<field name="SCENE_MODE_FIREWORKS" since="5" />
+		<field name="SCENE_MODE_HDR" since="17" />
 		<field name="SCENE_MODE_LANDSCAPE" since="5" />
 		<field name="SCENE_MODE_NIGHT" since="5" />
 		<field name="SCENE_MODE_NIGHT_PORTRAIT" since="5" />
@@ -9617,6 +9763,22 @@
 		<field name="SENSOR_TRICORDER" />
 		<field name="STANDARD_GRAVITY" />
 	</class>
+	<class name="android/hardware/display/DisplayManager" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="getDisplay(I)Landroid/view/Display;" />
+		<method name="getDisplays()[Landroid/view/Display;" />
+		<method name="getDisplays(Ljava/lang/String;)[Landroid/view/Display;" />
+		<method name="registerDisplayListener(Landroid/hardware/display/DisplayManager$DisplayListener;Landroid/os/Handler;)V" />
+		<method name="unregisterDisplayListener(Landroid/hardware/display/DisplayManager$DisplayListener;)V" />
+		<field name="DISPLAY_CATEGORY_PRESENTATION" />
+	</class>
+	<class name="android/hardware/display/DisplayManager$DisplayListener" since="17">
+		<extends name="java/lang/Object" />
+		<method name="onDisplayAdded(I)V" />
+		<method name="onDisplayChanged(I)V" />
+		<method name="onDisplayRemoved(I)V" />
+	</class>
 	<class name="android/hardware/input/InputManager" since="16">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -9774,6 +9936,7 @@
 		<method name="getKeyDispatcherState()Landroid/view/KeyEvent$DispatcherState;" since="5" />
 		<method name="onCreateInputMethodInterface()Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodImpl;" />
 		<method name="onCreateInputMethodSessionInterface()Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodSessionImpl;" />
+		<method name="onGenericMotionEvent(Landroid/view/MotionEvent;)Z" since="17" />
 		<method name="onTrackballEvent(Landroid/view/MotionEvent;)Z" />
 	</class>
 	<class name="android/inputmethodservice/AbstractInputMethodService$AbstractInputMethodImpl" since="3">
@@ -9802,6 +9965,7 @@
 	<class name="android/inputmethodservice/InputMethodService" since="3">
 		<extends name="android/inputmethodservice/AbstractInputMethodService" />
 		<method name="&lt;init>()V" />
+		<method name="enableHardwareAcceleration()Z" since="17" />
 		<method name="getBackDisposition()I" since="11" />
 		<method name="getCandidatesHiddenVisibility()I" />
 		<method name="getCurrentInputBinding()Landroid/view/inputmethod/InputBinding;" />
@@ -10094,6 +10258,13 @@
 		<method name="getFromLocationName(Ljava/lang/String;IDDDD)Ljava/util/List;" />
 		<method name="isPresent()Z" since="9" />
 	</class>
+	<class name="android/location/Geofence" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="createCircle(DDF)Landroid/location/Geofence;" />
+		<field name="CREATOR" />
+	</class>
 	<class name="android/location/GpsSatellite" since="3">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -10138,6 +10309,7 @@
 		<method name="getAccuracy()F" />
 		<method name="getAltitude()D" />
 		<method name="getBearing()F" />
+		<method name="getElapsedRealtimeNanos()J" since="17" />
 		<method name="getExtras()Landroid/os/Bundle;" />
 		<method name="getLatitude()D" />
 		<method name="getLongitude()D" />
@@ -10157,6 +10329,7 @@
 		<method name="setAccuracy(F)V" />
 		<method name="setAltitude(D)V" />
 		<method name="setBearing(F)V" />
+		<method name="setElapsedRealtimeNanos(J)V" since="17" />
 		<method name="setExtras(Landroid/os/Bundle;)V" />
 		<method name="setLatitude(D)V" />
 		<method name="setLongitude(D)V" />
@@ -10239,6 +10412,30 @@
 		<field name="OUT_OF_SERVICE" />
 		<field name="TEMPORARILY_UNAVAILABLE" />
 	</class>
+	<class name="android/location/LocationRequest" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="create()Landroid/location/LocationRequest;" />
+		<method name="getExpireAt()J" />
+		<method name="getFastestInterval()J" />
+		<method name="getInterval()J" />
+		<method name="getNumUpdates()I" />
+		<method name="getQuality()I" />
+		<method name="setExpireAt(J)Landroid/location/LocationRequest;" />
+		<method name="setExpireIn(J)Landroid/location/LocationRequest;" />
+		<method name="setFastestInterval(J)Landroid/location/LocationRequest;" />
+		<method name="setInterval(J)Landroid/location/LocationRequest;" />
+		<method name="setNumUpdates(I)Landroid/location/LocationRequest;" />
+		<method name="setQuality(I)Landroid/location/LocationRequest;" />
+		<field name="ACCURACY_BLOCK" />
+		<field name="ACCURACY_CITY" />
+		<field name="ACCURACY_FINE" />
+		<field name="CREATOR" />
+		<field name="POWER_HIGH" />
+		<field name="POWER_LOW" />
+		<field name="POWER_NONE" />
+	</class>
 	<class name="android/media/AsyncPlayer" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>(Ljava/lang/String;)V" />
@@ -10300,6 +10497,7 @@
 		<method name="adjustVolume(II)V" />
 		<method name="getMode()I" />
 		<method name="getParameters(Ljava/lang/String;)Ljava/lang/String;" since="5" />
+		<method name="getProperty(Ljava/lang/String;)Ljava/lang/String;" since="17" />
 		<method name="getRingerMode()I" />
 		<method name="getRouting(I)I" />
 		<method name="getStreamMaxVolume(I)I" />
@@ -10377,6 +10575,8 @@
 		<field name="MODE_NORMAL" />
 		<field name="MODE_RINGTONE" />
 		<field name="NUM_STREAMS" />
+		<field name="PROPERTY_OUTPUT_FRAMES_PER_BUFFER" since="17" />
+		<field name="PROPERTY_OUTPUT_SAMPLE_RATE" since="17" />
 		<field name="RINGER_MODE_CHANGED_ACTION" />
 		<field name="RINGER_MODE_NORMAL" />
 		<field name="RINGER_MODE_SILENT" />
@@ -10960,6 +11160,7 @@
 		<field name="METADATA_KEY_NUM_TRACKS" />
 		<field name="METADATA_KEY_TITLE" />
 		<field name="METADATA_KEY_VIDEO_HEIGHT" since="14" />
+		<field name="METADATA_KEY_VIDEO_ROTATION" since="17" />
 		<field name="METADATA_KEY_VIDEO_WIDTH" since="14" />
 		<field name="METADATA_KEY_WRITER" />
 		<field name="METADATA_KEY_YEAR" />
@@ -11021,15 +11222,20 @@
 		<method name="setWakeMode(Landroid/content/Context;I)V" />
 		<method name="start()V" />
 		<method name="stop()V" />
+		<field name="MEDIA_ERROR_IO" since="17" />
+		<field name="MEDIA_ERROR_MALFORMED" since="17" />
 		<field name="MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK" since="3" />
 		<field name="MEDIA_ERROR_SERVER_DIED" />
+		<field name="MEDIA_ERROR_TIMED_OUT" since="17" />
 		<field name="MEDIA_ERROR_UNKNOWN" />
+		<field name="MEDIA_ERROR_UNSUPPORTED" since="17" />
 		<field name="MEDIA_INFO_BAD_INTERLEAVING" since="3" />
 		<field name="MEDIA_INFO_BUFFERING_END" since="9" />
 		<field name="MEDIA_INFO_BUFFERING_START" since="9" />
 		<field name="MEDIA_INFO_METADATA_UPDATE" since="5" />
 		<field name="MEDIA_INFO_NOT_SEEKABLE" since="3" />
 		<field name="MEDIA_INFO_UNKNOWN" since="3" />
+		<field name="MEDIA_INFO_VIDEO_RENDERING_START" since="17" />
 		<field name="MEDIA_INFO_VIDEO_TRACK_LAGGING" since="3" />
 		<field name="MEDIA_MIMETYPE_TEXT_SUBRIP" since="16" />
 		<field name="VIDEO_SCALING_MODE_SCALE_TO_FIT" since="16" />
@@ -11113,6 +11319,7 @@
 		<method name="setVideoSource(I)V" since="3" />
 		<method name="start()V" />
 		<method name="stop()V" />
+		<field name="MEDIA_ERROR_SERVER_DIED" since="17" />
 		<field name="MEDIA_RECORDER_ERROR_UNKNOWN" since="3" />
 		<field name="MEDIA_RECORDER_INFO_MAX_DURATION_REACHED" since="3" />
 		<field name="MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED" since="3" />
@@ -11191,6 +11398,7 @@
 		<method name="removeUserRoute(Landroid/media/MediaRouter$UserRouteInfo;)V" />
 		<method name="selectRoute(ILandroid/media/MediaRouter$RouteInfo;)V" />
 		<field name="ROUTE_TYPE_LIVE_AUDIO" />
+		<field name="ROUTE_TYPE_LIVE_VIDEO" since="17" />
 		<field name="ROUTE_TYPE_USER" />
 	</class>
 	<class name="android/media/MediaRouter$Callback" since="16">
@@ -11199,6 +11407,7 @@
 		<method name="onRouteAdded(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;)V" />
 		<method name="onRouteChanged(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;)V" />
 		<method name="onRouteGrouped(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;Landroid/media/MediaRouter$RouteGroup;I)V" />
+		<method name="onRoutePresentationDisplayChanged(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;)V" since="17" />
 		<method name="onRouteRemoved(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;)V" />
 		<method name="onRouteSelected(Landroid/media/MediaRouter;ILandroid/media/MediaRouter$RouteInfo;)V" />
 		<method name="onRouteUngrouped(Landroid/media/MediaRouter;Landroid/media/MediaRouter$RouteInfo;Landroid/media/MediaRouter$RouteGroup;)V" />
@@ -11236,12 +11445,14 @@
 		<method name="getName(Landroid/content/Context;)Ljava/lang/CharSequence;" />
 		<method name="getPlaybackStream()I" />
 		<method name="getPlaybackType()I" />
+		<method name="getPresentationDisplay()Landroid/view/Display;" since="17" />
 		<method name="getStatus()Ljava/lang/CharSequence;" />
 		<method name="getSupportedTypes()I" />
 		<method name="getTag()Ljava/lang/Object;" />
 		<method name="getVolume()I" />
 		<method name="getVolumeHandling()I" />
 		<method name="getVolumeMax()I" />
+		<method name="isEnabled()Z" since="17" />
 		<method name="requestSetVolume(I)V" />
 		<method name="requestUpdateVolume(I)V" />
 		<method name="setTag(Ljava/lang/Object;)V" />
@@ -12010,6 +12221,7 @@
 		<field name="EXTRA_EXTRA_INFO" />
 		<field name="EXTRA_IS_FAILOVER" />
 		<field name="EXTRA_NETWORK_INFO" />
+		<field name="EXTRA_NETWORK_TYPE" since="17" />
 		<field name="EXTRA_NO_CONNECTIVITY" />
 		<field name="EXTRA_OTHER_NETWORK_INFO" />
 		<field name="EXTRA_REASON" />
@@ -12054,6 +12266,7 @@
 	</class>
 	<class name="android/net/LocalSocket" since="1">
 		<extends name="java/lang/Object" />
+		<implements name="java/io/Closeable" since="17" />
 		<method name="&lt;init>()V" />
 		<method name="bind(Landroid/net/LocalSocketAddress;)V" />
 		<method name="close()V" />
@@ -12133,6 +12346,7 @@
 		<method name="values()[Landroid/net/NetworkInfo$DetailedState;" />
 		<field name="AUTHENTICATING" />
 		<field name="BLOCKED" since="14" />
+		<field name="CAPTIVE_PORTAL_CHECK" since="17" />
 		<field name="CONNECTED" />
 		<field name="CONNECTING" />
 		<field name="DISCONNECTED" />
@@ -12177,9 +12391,11 @@
 		<method name="getHttpSocketFactory(ILandroid/net/SSLSessionCache;)Lorg/apache/http/conn/ssl/SSLSocketFactory;" since="8" />
 		<method name="getInsecure(ILandroid/net/SSLSessionCache;)Ljavax/net/ssl/SSLSocketFactory;" since="8" />
 		<method name="getNpnSelectedProtocol(Ljava/net/Socket;)[B" since="16" />
+		<method name="setHostname(Ljava/net/Socket;Ljava/lang/String;)V" since="17" />
 		<method name="setKeyManagers([Ljavax/net/ssl/KeyManager;)V" since="14" />
 		<method name="setNpnProtocols([[B)V" since="16" />
 		<method name="setTrustManagers([Ljavax/net/ssl/TrustManager;)V" since="14" />
+		<method name="setUseSessionTickets(Ljava/net/Socket;Z)V" since="17" />
 	</class>
 	<class name="android/net/SSLSessionCache" since="8">
 		<extends name="java/lang/Object" />
@@ -12451,34 +12667,10 @@
 		<field name="SSL_NOTYETVALID" />
 		<field name="SSL_UNTRUSTED" />
 	</class>
-	<class name="android/net/nsd/DnsSdServiceInfo" since="16">
+	<class name="android/net/http/X509TrustManagerExtensions" since="17">
 		<extends name="java/lang/Object" />
-		<implements name="android/os/Parcelable" />
-		<method name="&lt;init>()V" />
-		<method name="getHost()Ljava/net/InetAddress;" />
-		<method name="getPort()I" />
-		<method name="getServiceName()Ljava/lang/String;" />
-		<method name="getServiceType()Ljava/lang/String;" />
-		<method name="setHost(Ljava/net/InetAddress;)V" />
-		<method name="setPort(I)V" />
-		<method name="setServiceName(Ljava/lang/String;)V" />
-		<method name="setServiceType(Ljava/lang/String;)V" />
-		<field name="CREATOR" />
-	</class>
-	<class name="android/net/nsd/DnsSdTxtRecord" since="16">
-		<extends name="java/lang/Object" />
-		<implements name="android/os/Parcelable" />
-		<method name="&lt;init>()V" />
-		<method name="&lt;init>(Landroid/net/nsd/DnsSdTxtRecord;)V" />
-		<method name="&lt;init>([B)V" />
-		<method name="contains(Ljava/lang/String;)Z" />
-		<method name="get(Ljava/lang/String;)Ljava/lang/String;" />
-		<method name="getRawData()[B" />
-		<method name="keyCount()I" />
-		<method name="remove(Ljava/lang/String;)I" />
-		<method name="set(Ljava/lang/String;Ljava/lang/String;)V" />
-		<method name="size()I" />
-		<field name="CREATOR" />
+		<method name="&lt;init>(Ljavax/net/ssl/X509TrustManager;)V" />
+		<method name="checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;" />
 	</class>
 	<class name="android/net/nsd/NsdManager" since="16">
 		<extends name="java/lang/Object" />
@@ -12774,6 +12966,7 @@
 		<field name="capabilities" />
 		<field name="frequency" />
 		<field name="level" />
+		<field name="timestamp" since="17" />
 	</class>
 	<class name="android/net/wifi/SupplicantState" since="1">
 		<extends name="java/lang/Enum" />
@@ -13375,6 +13568,15 @@
 		<method name="getProtocolInfo()[B" />
 		<method name="transceive([B)[B" />
 	</class>
+	<class name="android/nfc/tech/NfcBarcode" since="17">
+		<extends name="android/nfc/tech/BasicTagTechnology" />
+		<method name="&lt;init>()V" />
+		<method name="get(Landroid/nfc/Tag;)Landroid/nfc/tech/NfcBarcode;" />
+		<method name="getBarcode()[B" />
+		<method name="getType()I" />
+		<field name="TYPE_KOVIO" />
+		<field name="TYPE_UNKNOWN" />
+	</class>
 	<class name="android/nfc/tech/NfcF" since="10">
 		<extends name="android/nfc/tech/BasicTagTechnology" />
 		<method name="&lt;init>()V" />
@@ -13402,6 +13604,178 @@
 		<method name="getTag()Landroid/nfc/Tag;" />
 		<method name="isConnected()Z" />
 	</class>
+	<class name="android/opengl/EGL14" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="eglBindAPI(I)Z" />
+		<method name="eglBindTexImage(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;I)Z" />
+		<method name="eglChooseConfig(Landroid/opengl/EGLDisplay;[II[Landroid/opengl/EGLConfig;II[II)Z" />
+		<method name="eglCopyBuffers(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;I)Z" />
+		<method name="eglCreateContext(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Landroid/opengl/EGLContext;[II)Landroid/opengl/EGLContext;" />
+		<method name="eglCreatePbufferFromClientBuffer(Landroid/opengl/EGLDisplay;IILandroid/opengl/EGLConfig;[II)Landroid/opengl/EGLSurface;" />
+		<method name="eglCreatePbufferSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;[II)Landroid/opengl/EGLSurface;" />
+		<method name="eglCreatePixmapSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;I[II)Landroid/opengl/EGLSurface;" />
+		<method name="eglCreateWindowSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface;" />
+		<method name="eglDestroyContext(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLContext;)Z" />
+		<method name="eglDestroySurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;)Z" />
+		<method name="eglGetConfigAttrib(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;I[II)Z" />
+		<method name="eglGetConfigs(Landroid/opengl/EGLDisplay;[Landroid/opengl/EGLConfig;II[II)Z" />
+		<method name="eglGetCurrentContext()Landroid/opengl/EGLContext;" />
+		<method name="eglGetCurrentDisplay()Landroid/opengl/EGLDisplay;" />
+		<method name="eglGetCurrentSurface(I)Landroid/opengl/EGLSurface;" />
+		<method name="eglGetDisplay(I)Landroid/opengl/EGLDisplay;" />
+		<method name="eglGetError()I" />
+		<method name="eglInitialize(Landroid/opengl/EGLDisplay;[II[II)Z" />
+		<method name="eglMakeCurrent(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;Landroid/opengl/EGLSurface;Landroid/opengl/EGLContext;)Z" />
+		<method name="eglQueryAPI()I" />
+		<method name="eglQueryContext(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLContext;I[II)Z" />
+		<method name="eglQueryString(Landroid/opengl/EGLDisplay;I)Ljava/lang/String;" />
+		<method name="eglQuerySurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;I[II)Z" />
+		<method name="eglReleaseTexImage(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;I)Z" />
+		<method name="eglReleaseThread()Z" />
+		<method name="eglSurfaceAttrib(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;II)Z" />
+		<method name="eglSwapBuffers(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;)Z" />
+		<method name="eglSwapInterval(Landroid/opengl/EGLDisplay;I)Z" />
+		<method name="eglTerminate(Landroid/opengl/EGLDisplay;)Z" />
+		<method name="eglWaitClient()Z" />
+		<method name="eglWaitGL()Z" />
+		<method name="eglWaitNative(I)Z" />
+		<field name="EGL_ALPHA_MASK_SIZE" />
+		<field name="EGL_ALPHA_SIZE" />
+		<field name="EGL_BACK_BUFFER" />
+		<field name="EGL_BAD_ACCESS" />
+		<field name="EGL_BAD_ALLOC" />
+		<field name="EGL_BAD_ATTRIBUTE" />
+		<field name="EGL_BAD_CONFIG" />
+		<field name="EGL_BAD_CONTEXT" />
+		<field name="EGL_BAD_CURRENT_SURFACE" />
+		<field name="EGL_BAD_DISPLAY" />
+		<field name="EGL_BAD_MATCH" />
+		<field name="EGL_BAD_NATIVE_PIXMAP" />
+		<field name="EGL_BAD_NATIVE_WINDOW" />
+		<field name="EGL_BAD_PARAMETER" />
+		<field name="EGL_BAD_SURFACE" />
+		<field name="EGL_BIND_TO_TEXTURE_RGB" />
+		<field name="EGL_BIND_TO_TEXTURE_RGBA" />
+		<field name="EGL_BLUE_SIZE" />
+		<field name="EGL_BUFFER_DESTROYED" />
+		<field name="EGL_BUFFER_PRESERVED" />
+		<field name="EGL_BUFFER_SIZE" />
+		<field name="EGL_CLIENT_APIS" />
+		<field name="EGL_COLOR_BUFFER_TYPE" />
+		<field name="EGL_CONFIG_CAVEAT" />
+		<field name="EGL_CONFIG_ID" />
+		<field name="EGL_CONFORMANT" />
+		<field name="EGL_CONTEXT_CLIENT_TYPE" />
+		<field name="EGL_CONTEXT_CLIENT_VERSION" />
+		<field name="EGL_CONTEXT_LOST" />
+		<field name="EGL_CORE_NATIVE_ENGINE" />
+		<field name="EGL_DEFAULT_DISPLAY" />
+		<field name="EGL_DEPTH_SIZE" />
+		<field name="EGL_DISPLAY_SCALING" />
+		<field name="EGL_DRAW" />
+		<field name="EGL_EXTENSIONS" />
+		<field name="EGL_FALSE" />
+		<field name="EGL_GREEN_SIZE" />
+		<field name="EGL_HEIGHT" />
+		<field name="EGL_HORIZONTAL_RESOLUTION" />
+		<field name="EGL_LARGEST_PBUFFER" />
+		<field name="EGL_LEVEL" />
+		<field name="EGL_LUMINANCE_BUFFER" />
+		<field name="EGL_LUMINANCE_SIZE" />
+		<field name="EGL_MATCH_NATIVE_PIXMAP" />
+		<field name="EGL_MAX_PBUFFER_HEIGHT" />
+		<field name="EGL_MAX_PBUFFER_PIXELS" />
+		<field name="EGL_MAX_PBUFFER_WIDTH" />
+		<field name="EGL_MAX_SWAP_INTERVAL" />
+		<field name="EGL_MIN_SWAP_INTERVAL" />
+		<field name="EGL_MIPMAP_LEVEL" />
+		<field name="EGL_MIPMAP_TEXTURE" />
+		<field name="EGL_MULTISAMPLE_RESOLVE" />
+		<field name="EGL_MULTISAMPLE_RESOLVE_BOX" />
+		<field name="EGL_MULTISAMPLE_RESOLVE_BOX_BIT" />
+		<field name="EGL_MULTISAMPLE_RESOLVE_DEFAULT" />
+		<field name="EGL_NATIVE_RENDERABLE" />
+		<field name="EGL_NATIVE_VISUAL_ID" />
+		<field name="EGL_NATIVE_VISUAL_TYPE" />
+		<field name="EGL_NONE" />
+		<field name="EGL_NON_CONFORMANT_CONFIG" />
+		<field name="EGL_NOT_INITIALIZED" />
+		<field name="EGL_NO_CONTEXT" />
+		<field name="EGL_NO_DISPLAY" />
+		<field name="EGL_NO_SURFACE" />
+		<field name="EGL_NO_TEXTURE" />
+		<field name="EGL_OPENGL_API" />
+		<field name="EGL_OPENGL_BIT" />
+		<field name="EGL_OPENGL_ES2_BIT" />
+		<field name="EGL_OPENGL_ES_API" />
+		<field name="EGL_OPENGL_ES_BIT" />
+		<field name="EGL_OPENVG_API" />
+		<field name="EGL_OPENVG_BIT" />
+		<field name="EGL_OPENVG_IMAGE" />
+		<field name="EGL_PBUFFER_BIT" />
+		<field name="EGL_PIXEL_ASPECT_RATIO" />
+		<field name="EGL_PIXMAP_BIT" />
+		<field name="EGL_READ" />
+		<field name="EGL_RED_SIZE" />
+		<field name="EGL_RENDERABLE_TYPE" />
+		<field name="EGL_RENDER_BUFFER" />
+		<field name="EGL_RGB_BUFFER" />
+		<field name="EGL_SAMPLES" />
+		<field name="EGL_SAMPLE_BUFFERS" />
+		<field name="EGL_SINGLE_BUFFER" />
+		<field name="EGL_SLOW_CONFIG" />
+		<field name="EGL_STENCIL_SIZE" />
+		<field name="EGL_SUCCESS" />
+		<field name="EGL_SURFACE_TYPE" />
+		<field name="EGL_SWAP_BEHAVIOR" />
+		<field name="EGL_SWAP_BEHAVIOR_PRESERVED_BIT" />
+		<field name="EGL_TEXTURE_2D" />
+		<field name="EGL_TEXTURE_FORMAT" />
+		<field name="EGL_TEXTURE_RGB" />
+		<field name="EGL_TEXTURE_RGBA" />
+		<field name="EGL_TEXTURE_TARGET" />
+		<field name="EGL_TRANSPARENT_BLUE_VALUE" />
+		<field name="EGL_TRANSPARENT_GREEN_VALUE" />
+		<field name="EGL_TRANSPARENT_RED_VALUE" />
+		<field name="EGL_TRANSPARENT_RGB" />
+		<field name="EGL_TRANSPARENT_TYPE" />
+		<field name="EGL_TRUE" />
+		<field name="EGL_VENDOR" />
+		<field name="EGL_VERSION" />
+		<field name="EGL_VERTICAL_RESOLUTION" />
+		<field name="EGL_VG_ALPHA_FORMAT" />
+		<field name="EGL_VG_ALPHA_FORMAT_NONPRE" />
+		<field name="EGL_VG_ALPHA_FORMAT_PRE" />
+		<field name="EGL_VG_ALPHA_FORMAT_PRE_BIT" />
+		<field name="EGL_VG_COLORSPACE" />
+		<field name="EGL_VG_COLORSPACE_LINEAR" />
+		<field name="EGL_VG_COLORSPACE_LINEAR_BIT" />
+		<field name="EGL_VG_COLORSPACE_sRGB" />
+		<field name="EGL_WIDTH" />
+		<field name="EGL_WINDOW_BIT" />
+	</class>
+	<class name="android/opengl/EGLConfig" since="17">
+		<extends name="android/opengl/EGLObjectHandle" />
+		<method name="&lt;init>()V" />
+	</class>
+	<class name="android/opengl/EGLContext" since="17">
+		<extends name="android/opengl/EGLObjectHandle" />
+		<method name="&lt;init>()V" />
+	</class>
+	<class name="android/opengl/EGLDisplay" since="17">
+		<extends name="android/opengl/EGLObjectHandle" />
+		<method name="&lt;init>()V" />
+	</class>
+	<class name="android/opengl/EGLObjectHandle" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>(I)V" />
+		<method name="getHandle()I" />
+	</class>
+	<class name="android/opengl/EGLSurface" since="17">
+		<extends name="android/opengl/EGLObjectHandle" />
+		<method name="&lt;init>()V" />
+	</class>
 	<class name="android/opengl/ETC1" since="8">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -14310,8 +14684,12 @@
 		<method name="glGenerateMipmap(I)V" />
 		<method name="glGetActiveAttrib(IIILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;B)V" />
 		<method name="glGetActiveAttrib(III[II[II[II[BI)V" />
+		<method name="glGetActiveAttrib(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)Ljava/lang/String;" since="17" />
+		<method name="glGetActiveAttrib(II[II[II)Ljava/lang/String;" since="17" />
 		<method name="glGetActiveUniform(IIILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;B)V" />
 		<method name="glGetActiveUniform(III[II[II[II[BI)V" />
+		<method name="glGetActiveUniform(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)Ljava/lang/String;" since="17" />
+		<method name="glGetActiveUniform(II[II[II)Ljava/lang/String;" since="17" />
 		<method name="glGetAttachedShaders(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)V" />
 		<method name="glGetAttachedShaders(II[II[II)V" />
 		<method name="glGetAttribLocation(ILjava/lang/String;)I" />
@@ -14334,6 +14712,7 @@
 		<method name="glGetShaderInfoLog(I)Ljava/lang/String;" />
 		<method name="glGetShaderPrecisionFormat(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)V" />
 		<method name="glGetShaderPrecisionFormat(II[II[II)V" />
+		<method name="glGetShaderSource(I)Ljava/lang/String;" since="17" />
 		<method name="glGetShaderSource(IILjava/nio/IntBuffer;B)V" />
 		<method name="glGetShaderSource(II[II[BI)V" />
 		<method name="glGetShaderiv(IILjava/nio/IntBuffer;)V" />
@@ -14890,6 +15269,7 @@
 		<field name="BATTERY_HEALTH_UNSPECIFIED_FAILURE" />
 		<field name="BATTERY_PLUGGED_AC" />
 		<field name="BATTERY_PLUGGED_USB" />
+		<field name="BATTERY_PLUGGED_WIRELESS" since="17" />
 		<field name="BATTERY_STATUS_CHARGING" />
 		<field name="BATTERY_STATUS_DISCHARGING" />
 		<field name="BATTERY_STATUS_FULL" />
@@ -14916,6 +15296,7 @@
 		<method name="flushPendingCommands()V" />
 		<method name="getCallingPid()I" />
 		<method name="getCallingUid()I" />
+		<method name="getCallingUserHandle()Landroid/os/UserHandle;" since="17" />
 		<method name="joinThreadPool()V" />
 		<method name="onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z" />
 		<method name="restoreCallingIdentity(J)V" />
@@ -14975,6 +15356,7 @@
 		<field name="ICE_CREAM_SANDWICH" since="14" />
 		<field name="ICE_CREAM_SANDWICH_MR1" since="15" />
 		<field name="JELLY_BEAN" since="16" />
+		<field name="JELLY_BEAN_MR1" since="17" />
 	</class>
 	<class name="android/os/Bundle" since="1">
 		<extends name="java/lang/Object" />
@@ -15640,6 +16022,7 @@
 		<method name="newWakeLock(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;" />
 		<method name="reboot(Ljava/lang/String;)V" since="8" />
 		<method name="userActivity(JZ)V" />
+		<method name="wakeUp(J)V" since="17" />
 		<field name="ACQUIRE_CAUSES_WAKEUP" />
 		<field name="FULL_WAKE_LOCK" />
 		<field name="ON_AFTER_RELEASE" />
@@ -15668,6 +16051,7 @@
 		<method name="myPid()I" />
 		<method name="myTid()I" />
 		<method name="myUid()I" since="2" />
+		<method name="myUserHandle()Landroid/os/UserHandle;" since="17" />
 		<method name="sendSignal(II)V" />
 		<method name="setThreadPriority(I)V" />
 		<method name="setThreadPriority(II)V" />
@@ -15710,6 +16094,7 @@
 		<method name="finishBroadcast()V" />
 		<method name="getBroadcastCookie(I)Ljava/lang/Object;" since="4" />
 		<method name="getBroadcastItem(I)Landroid/os/IInterface;" />
+		<method name="getRegisteredCallbackCount()I" since="17" />
 		<method name="kill()V" />
 		<method name="onCallbackDied(Landroid/os/IInterface;)V" />
 		<method name="onCallbackDied(Landroid/os/IInterface;Ljava/lang/Object;)V" since="4" />
@@ -15803,6 +16188,7 @@
 		<method name="&lt;init>()V" />
 		<method name="currentThreadTimeMillis()J" />
 		<method name="elapsedRealtime()J" />
+		<method name="elapsedRealtimeNanos()J" since="17" />
 		<method name="setCurrentTimeMillis(J)Z" />
 		<method name="sleep(J)V" />
 		<method name="uptimeMillis()J" />
@@ -15823,6 +16209,25 @@
 		<extends name="android/os/RemoteException" />
 		<method name="&lt;init>()V" />
 	</class>
+	<class name="android/os/UserHandle" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>(Landroid/os/Parcel;)V" />
+		<method name="readFromParcel(Landroid/os/Parcel;)Landroid/os/UserHandle;" />
+		<method name="writeToParcel(Landroid/os/UserHandle;Landroid/os/Parcel;)V" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/os/UserManager" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="getSerialNumberForUser(Landroid/os/UserHandle;)J" />
+		<method name="getUserCount()I" />
+		<method name="getUserForSerialNumber(J)Landroid/os/UserHandle;" />
+		<method name="getUserName()Ljava/lang/String;" />
+		<method name="isUserAGoat()Z" />
+		<method name="isUserRunning(Landroid/os/UserHandle;)Z" />
+		<method name="isUserRunningOrStopping(Landroid/os/UserHandle;)Z" />
+	</class>
 	<class name="android/os/Vibrator" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -16418,6 +16823,7 @@
 		<field name="CAL_ACCESS_ROOT" />
 		<field name="CAN_MODIFY_TIME_ZONE" />
 		<field name="CAN_ORGANIZER_RESPOND" />
+		<field name="IS_PRIMARY" since="17" />
 		<field name="MAX_REMINDERS" />
 		<field name="OWNER_ACCOUNT" />
 		<field name="SYNC_EVENTS" />
@@ -16527,6 +16933,7 @@
 		<field name="HAS_ALARM" />
 		<field name="HAS_ATTENDEE_DATA" />
 		<field name="HAS_EXTENDED_PROPERTIES" />
+		<field name="IS_ORGANIZER" since="17" />
 		<field name="LAST_DATE" />
 		<field name="LAST_SYNCED" />
 		<field name="ORGANIZER" />
@@ -16552,6 +16959,7 @@
 		<field name="SYNC_DATA8" />
 		<field name="SYNC_DATA9" />
 		<field name="TITLE" />
+		<field name="UID_2445" since="17" />
 	</class>
 	<class name="android/provider/CalendarContract$EventsEntity" since="14">
 		<extends name="java/lang/Object" />
@@ -16657,9 +17065,11 @@
 		<field name="DURATION" />
 		<field name="INCOMING_TYPE" />
 		<field name="IS_READ" since="14" />
+		<field name="LIMIT_PARAM_KEY" since="17" />
 		<field name="MISSED_TYPE" />
 		<field name="NEW" />
 		<field name="NUMBER" />
+		<field name="OFFSET_PARAM_KEY" since="17" />
 		<field name="OUTGOING_TYPE" />
 		<field name="TYPE" />
 	</class>
@@ -17880,6 +18290,7 @@
 		<method name="getMediaScannerUri()Landroid/net/Uri;" />
 		<method name="getVersion(Landroid/content/Context;)Ljava/lang/String;" since="12" />
 		<field name="ACTION_IMAGE_CAPTURE" since="3" />
+		<field name="ACTION_IMAGE_CAPTURE_SECURE" since="17" />
 		<field name="ACTION_VIDEO_CAPTURE" since="3" />
 		<field name="AUTHORITY" />
 		<field name="EXTRA_DURATION_LIMIT" since="8" />
@@ -17898,7 +18309,10 @@
 		<field name="INTENT_ACTION_MEDIA_SEARCH" since="3" />
 		<field name="INTENT_ACTION_MUSIC_PLAYER" since="8" />
 		<field name="INTENT_ACTION_STILL_IMAGE_CAMERA" since="3" />
+		<field name="INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE" since="17" />
+		<field name="INTENT_ACTION_TEXT_OPEN_FROM_SEARCH" since="17" />
 		<field name="INTENT_ACTION_VIDEO_CAMERA" since="3" />
+		<field name="INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH" since="17" />
 		<field name="MEDIA_IGNORE_FILENAME" since="9" />
 		<field name="MEDIA_SCANNER_VOLUME" />
 		<field name="UNKNOWN_STRING" since="8" />
@@ -18258,6 +18672,62 @@
 		<field name="EXTRA_AUTHORITIES" since="8" />
 		<field name="EXTRA_INPUT_METHOD_ID" since="11" />
 	</class>
+	<class name="android/provider/Settings$Global" since="17">
+		<extends name="android/provider/Settings$NameValueTable" />
+		<method name="&lt;init>()V" />
+		<method name="getFloat(Landroid/content/ContentResolver;Ljava/lang/String;)F" />
+		<method name="getFloat(Landroid/content/ContentResolver;Ljava/lang/String;F)F" />
+		<method name="getInt(Landroid/content/ContentResolver;Ljava/lang/String;)I" />
+		<method name="getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I" />
+		<method name="getLong(Landroid/content/ContentResolver;Ljava/lang/String;)J" />
+		<method name="getLong(Landroid/content/ContentResolver;Ljava/lang/String;J)J" />
+		<method name="getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;" />
+		<method name="getUriFor(Ljava/lang/String;)Landroid/net/Uri;" />
+		<method name="putFloat(Landroid/content/ContentResolver;Ljava/lang/String;F)Z" />
+		<method name="putInt(Landroid/content/ContentResolver;Ljava/lang/String;I)Z" />
+		<method name="putLong(Landroid/content/ContentResolver;Ljava/lang/String;J)Z" />
+		<method name="putString(Landroid/content/ContentResolver;Ljava/lang/String;Ljava/lang/String;)Z" />
+		<field name="ADB_ENABLED" />
+		<field name="AIRPLANE_MODE_ON" />
+		<field name="AIRPLANE_MODE_RADIOS" />
+		<field name="ALWAYS_FINISH_ACTIVITIES" />
+		<field name="ANIMATOR_DURATION_SCALE" />
+		<field name="AUTO_TIME" />
+		<field name="AUTO_TIME_ZONE" />
+		<field name="BLUETOOTH_ON" />
+		<field name="CONTENT_URI" />
+		<field name="DATA_ROAMING" />
+		<field name="DEBUG_APP" />
+		<field name="DEVELOPMENT_SETTINGS_ENABLED" />
+		<field name="DEVICE_PROVISIONED" />
+		<field name="HTTP_PROXY" />
+		<field name="INSTALL_NON_MARKET_APPS" />
+		<field name="MODE_RINGER" />
+		<field name="NETWORK_PREFERENCE" />
+		<field name="RADIO_BLUETOOTH" />
+		<field name="RADIO_CELL" />
+		<field name="RADIO_NFC" />
+		<field name="RADIO_WIFI" />
+		<field name="SHOW_PROCESSES" />
+		<field name="STAY_ON_WHILE_PLUGGED_IN" />
+		<field name="SYS_PROP_SETTING_VERSION" />
+		<field name="TRANSITION_ANIMATION_SCALE" />
+		<field name="USB_MASS_STORAGE_ENABLED" />
+		<field name="USE_GOOGLE_MAIL" />
+		<field name="WAIT_FOR_DEBUGGER" />
+		<field name="WIFI_MAX_DHCP_RETRY_COUNT" />
+		<field name="WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS" />
+		<field name="WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON" />
+		<field name="WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY" />
+		<field name="WIFI_NUM_OPEN_NETWORKS_KEPT" />
+		<field name="WIFI_ON" />
+		<field name="WIFI_SLEEP_POLICY" />
+		<field name="WIFI_SLEEP_POLICY_DEFAULT" />
+		<field name="WIFI_SLEEP_POLICY_NEVER" />
+		<field name="WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED" />
+		<field name="WIFI_WATCHDOG_ON" />
+		<field name="WINDOW_ANIMATION_SCALE" />
+	</class>
 	<class name="android/provider/Settings$NameValueTable" since="1">
 		<extends name="java/lang/Object" />
 		<implements name="android/provider/BaseColumns" />
@@ -19485,6 +19955,8 @@
 		<extends name="android/renderscript/BaseObj" />
 		<method name="&lt;init>()V" />
 		<method name="bindAllocation(Landroid/renderscript/Allocation;I)V" />
+		<method name="createFieldID(ILandroid/renderscript/Element;)Landroid/renderscript/Script$FieldID;" since="17" />
+		<method name="createKernelID(IILandroid/renderscript/Element;Landroid/renderscript/Element;)Landroid/renderscript/Script$KernelID;" since="17" />
 		<method name="forEach(ILandroid/renderscript/Allocation;Landroid/renderscript/Allocation;Landroid/renderscript/FieldPacker;)V" since="14" />
 		<method name="invoke(I)V" />
 		<method name="invoke(ILandroid/renderscript/FieldPacker;)V" />
@@ -19514,11 +19986,135 @@
 		<field name="mAllocation" />
 		<field name="mElement" />
 	</class>
+	<class name="android/renderscript/Script$FieldID" since="17">
+		<extends name="android/renderscript/BaseObj" />
+		<method name="&lt;init>()V" />
+	</class>
+	<class name="android/renderscript/Script$KernelID" since="17">
+		<extends name="android/renderscript/BaseObj" />
+		<method name="&lt;init>()V" />
+	</class>
 	<class name="android/renderscript/ScriptC" since="11">
 		<extends name="android/renderscript/Script" />
 		<method name="&lt;init>(ILandroid/renderscript/RenderScript;)V" />
 		<method name="&lt;init>(Landroid/renderscript/RenderScript;Landroid/content/res/Resources;I)V" />
 	</class>
+	<class name="android/renderscript/ScriptGroup" since="17">
+		<extends name="android/renderscript/BaseObj" />
+		<method name="&lt;init>()V" />
+		<method name="execute()V" />
+		<method name="setInput(Landroid/renderscript/Script$KernelID;Landroid/renderscript/Allocation;)V" />
+		<method name="setOutput(Landroid/renderscript/Script$KernelID;Landroid/renderscript/Allocation;)V" />
+	</class>
+	<class name="android/renderscript/ScriptGroup$Builder" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>(Landroid/renderscript/RenderScript;)V" />
+		<method name="addConnection(Landroid/renderscript/Type;Landroid/renderscript/Script$KernelID;Landroid/renderscript/Script$FieldID;)Landroid/renderscript/ScriptGroup$Builder;" />
+		<method name="addConnection(Landroid/renderscript/Type;Landroid/renderscript/Script$KernelID;Landroid/renderscript/Script$KernelID;)Landroid/renderscript/ScriptGroup$Builder;" />
+		<method name="addKernel(Landroid/renderscript/Script$KernelID;)Landroid/renderscript/ScriptGroup$Builder;" />
+		<method name="create()Landroid/renderscript/ScriptGroup;" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsic" since="17">
+		<extends name="android/renderscript/Script" />
+		<method name="&lt;init>()V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicBlend" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicBlend;" />
+		<method name="forEachAdd(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachClear(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachDst(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachDstAtop(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachDstIn(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachDstOut(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachDstOver(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachMultiply(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSrc(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSrcAtop(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSrcIn(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSrcOut(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSrcOver(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachSubtract(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="forEachXor(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="getKernelIDAdd()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDClear()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDDst()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDDstAtop()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDDstIn()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDDstOut()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDDstOver()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDMultiply()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSrc()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSrcAtop()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSrcIn()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSrcOut()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSrcOver()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDSubtract()Landroid/renderscript/Script$KernelID;" />
+		<method name="getKernelIDXor()Landroid/renderscript/Script$KernelID;" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicBlur" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicBlur;" />
+		<method name="forEach(Landroid/renderscript/Allocation;)V" />
+		<method name="getFieldID_Input()Landroid/renderscript/Script$FieldID;" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setInput(Landroid/renderscript/Allocation;)V" />
+		<method name="setRadius(F)V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicColorMatrix" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicColorMatrix;" />
+		<method name="forEach(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setColorMatrix(Landroid/renderscript/Matrix3f;)V" />
+		<method name="setColorMatrix(Landroid/renderscript/Matrix4f;)V" />
+		<method name="setGreyscale()V" />
+		<method name="setRGBtoYUV()V" />
+		<method name="setYUVtoRGB()V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicConvolve3x3" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicConvolve3x3;" />
+		<method name="forEach(Landroid/renderscript/Allocation;)V" />
+		<method name="getFieldID_Input()Landroid/renderscript/Script$FieldID;" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setCoefficients([F)V" />
+		<method name="setInput(Landroid/renderscript/Allocation;)V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicConvolve5x5" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicConvolve5x5;" />
+		<method name="forEach(Landroid/renderscript/Allocation;)V" />
+		<method name="getFieldID_Input()Landroid/renderscript/Script$FieldID;" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setCoefficients([F)V" />
+		<method name="setInput(Landroid/renderscript/Allocation;)V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicLUT" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicLUT;" />
+		<method name="forEach(Landroid/renderscript/Allocation;Landroid/renderscript/Allocation;)V" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setAlpha(II)V" />
+		<method name="setBlue(II)V" />
+		<method name="setGreen(II)V" />
+		<method name="setRed(II)V" />
+	</class>
+	<class name="android/renderscript/ScriptIntrinsicYuvToRGB" since="17">
+		<extends name="android/renderscript/ScriptIntrinsic" />
+		<method name="&lt;init>()V" />
+		<method name="create(Landroid/renderscript/RenderScript;Landroid/renderscript/Element;)Landroid/renderscript/ScriptIntrinsicYuvToRGB;" />
+		<method name="forEach(Landroid/renderscript/Allocation;)V" />
+		<method name="getFieldID_Input()Landroid/renderscript/Script$FieldID;" />
+		<method name="getKernelID()Landroid/renderscript/Script$KernelID;" />
+		<method name="setInput(Landroid/renderscript/Allocation;)V" />
+	</class>
 	<class name="android/renderscript/Short2" since="11">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -19618,6 +20214,11 @@
 		<implements name="android/sax/EndTextElementListener" />
 		<implements name="android/sax/StartElementListener" />
 	</class>
+	<class name="android/security/AndroidKeyPairGeneratorSpec" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="java/security/spec/AlgorithmParameterSpec" />
+		<method name="&lt;init>(Landroid/content/Context;Ljava/lang/String;Ljavax/security/auth/x500/X500Principal;Ljava/math/BigInteger;Ljava/util/Date;Ljava/util/Date;)V" />
+	</class>
 	<class name="android/security/KeyChain" since="14">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -19641,6 +20242,55 @@
 		<method name="&lt;init>(Ljava/lang/String;Ljava/lang/Throwable;)V" />
 		<method name="&lt;init>(Ljava/lang/Throwable;)V" />
 	</class>
+	<class name="android/service/dreams/Dream" since="17">
+		<extends name="android/app/Service" />
+		<implements name="android/view/Window$Callback" />
+		<method name="&lt;init>()V" />
+		<method name="addContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V" />
+		<method name="findViewById(I)Landroid/view/View;" />
+		<method name="finish()V" />
+		<method name="getWindow()Landroid/view/Window;" />
+		<method name="getWindowManager()Landroid/view/WindowManager;" />
+		<method name="isFullscreen()Z" />
+		<method name="isInteractive()Z" />
+		<method name="isLowProfile()Z" />
+		<method name="isScreenBright()Z" />
+		<method name="onStart()V" />
+		<method name="setContentView(I)V" />
+		<method name="setContentView(Landroid/view/View;)V" />
+		<method name="setContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V" />
+		<method name="setFullscreen(Z)V" />
+		<method name="setInteractive(Z)V" />
+		<method name="setLowProfile(Z)V" />
+		<method name="setScreenBright(Z)V" />
+		<field name="ACTION_DREAMING_STARTED" />
+		<field name="ACTION_DREAMING_STOPPED" />
+		<field name="CATEGORY_DREAM" />
+		<field name="DREAM_META_DATA" />
+	</class>
+	<class name="android/service/dreams/DreamService" since="17">
+		<extends name="android/app/Service" />
+		<implements name="android/view/Window$Callback" />
+		<method name="&lt;init>()V" />
+		<method name="addContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V" />
+		<method name="findViewById(I)Landroid/view/View;" />
+		<method name="finish()V" />
+		<method name="getWindow()Landroid/view/Window;" />
+		<method name="getWindowManager()Landroid/view/WindowManager;" />
+		<method name="isFullscreen()Z" />
+		<method name="isInteractive()Z" />
+		<method name="isScreenBright()Z" />
+		<method name="onDreamingStarted()V" />
+		<method name="onDreamingStopped()V" />
+		<method name="setContentView(I)V" />
+		<method name="setContentView(Landroid/view/View;)V" />
+		<method name="setContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V" />
+		<method name="setFullscreen(Z)V" />
+		<method name="setInteractive(Z)V" />
+		<method name="setScreenBright(Z)V" />
+		<field name="DREAM_META_DATA" />
+		<field name="SERVICE_INTERFACE" />
+	</class>
 	<class name="android/service/textservice/SpellCheckerService" since="14">
 		<extends name="android/app/Service" />
 		<method name="&lt;init>()V" />
@@ -19909,12 +20559,116 @@
 		<method name="onError(Ljava/lang/String;)V" />
 		<method name="onStart(Ljava/lang/String;)V" />
 	</class>
+	<class name="android/telephony/CellIdentity" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellIdentityCdma" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getBasestationId()I" />
+		<method name="getLatitude()I" />
+		<method name="getLongitude()I" />
+		<method name="getNetworkId()I" />
+		<method name="getSystemId()I" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellIdentityGsm" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCid()I" />
+		<method name="getLac()I" />
+		<method name="getMcc()I" />
+		<method name="getMnc()I" />
+		<method name="getPsc()I" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellIdentityLte" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCi()I" />
+		<method name="getMcc()I" />
+		<method name="getMnc()I" />
+		<method name="getPci()I" />
+		<method name="getTac()I" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellInfo" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getTimeStamp()J" />
+		<method name="isRegistered()Z" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellInfoCdma" since="17">
+		<extends name="android/telephony/CellInfo" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCellIdentity()Landroid/telephony/CellIdentityCdma;" />
+		<method name="getCellSignalStrength()Landroid/telephony/CellSignalStrengthCdma;" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellInfoGsm" since="17">
+		<extends name="android/telephony/CellInfo" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCellIdentity()Landroid/telephony/CellIdentityGsm;" />
+		<method name="getCellSignalStrength()Landroid/telephony/CellSignalStrengthGsm;" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellInfoLte" since="17">
+		<extends name="android/telephony/CellInfo" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCellIdentity()Landroid/telephony/CellIdentityLte;" />
+		<method name="getCellSignalStrength()Landroid/telephony/CellSignalStrengthLte;" />
+		<field name="CREATOR" />
+	</class>
 	<class name="android/telephony/CellLocation" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
 		<method name="getEmpty()Landroid/telephony/CellLocation;" />
 		<method name="requestLocationUpdate()V" />
 	</class>
+	<class name="android/telephony/CellSignalStrength" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="getAsuLevel()I" />
+		<method name="getDbm()I" />
+		<method name="getLevel()I" />
+	</class>
+	<class name="android/telephony/CellSignalStrengthCdma" since="17">
+		<extends name="android/telephony/CellSignalStrength" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getCdmaDbm()I" />
+		<method name="getCdmaEcio()I" />
+		<method name="getCdmaLevel()I" />
+		<method name="getEvdoDbm()I" />
+		<method name="getEvdoEcio()I" />
+		<method name="getEvdoLevel()I" />
+		<method name="getEvdoSnr()I" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellSignalStrengthGsm" since="17">
+		<extends name="android/telephony/CellSignalStrength" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<field name="CREATOR" />
+	</class>
+	<class name="android/telephony/CellSignalStrengthLte" since="17">
+		<extends name="android/telephony/CellSignalStrength" />
+		<implements name="android/os/Parcelable" />
+		<method name="&lt;init>()V" />
+		<method name="getTimingAdvance()I" />
+		<field name="CREATOR" />
+	</class>
 	<class name="android/telephony/NeighboringCellInfo" since="3">
 		<extends name="java/lang/Object" />
 		<implements name="android/os/Parcelable" />
@@ -19985,6 +20739,7 @@
 		<method name="&lt;init>()V" />
 		<method name="onCallForwardingIndicatorChanged(Z)V" />
 		<method name="onCallStateChanged(ILjava/lang/String;)V" />
+		<method name="onCellInfoChanged(Ljava/util/List;)V" since="17" />
 		<method name="onCellLocationChanged(Landroid/telephony/CellLocation;)V" />
 		<method name="onDataActivity(I)V" />
 		<method name="onDataConnectionStateChanged(I)V" />
@@ -19995,6 +20750,7 @@
 		<method name="onSignalStrengthsChanged(Landroid/telephony/SignalStrength;)V" since="7" />
 		<field name="LISTEN_CALL_FORWARDING_INDICATOR" />
 		<field name="LISTEN_CALL_STATE" />
+		<field name="LISTEN_CELL_INFO" since="17" />
 		<field name="LISTEN_CELL_LOCATION" />
 		<field name="LISTEN_DATA_ACTIVITY" />
 		<field name="LISTEN_DATA_CONNECTION_STATE" />
@@ -20123,6 +20879,7 @@
 	<class name="android/telephony/TelephonyManager" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
+		<method name="getAllCellInfo()Ljava/util/List;" since="17" />
 		<method name="getCallState()I" />
 		<method name="getCellLocation()Landroid/telephony/CellLocation;" />
 		<method name="getDataActivity()I" />
@@ -20196,6 +20953,7 @@
 		<extends name="android/telephony/CellLocation" />
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(Landroid/os/Bundle;)V" />
+		<method name="convertQuartSecToDecDegrees(I)D" since="17" />
 		<method name="fillInNotifierBundle(Landroid/os/Bundle;)V" />
 		<method name="getBaseStationId()I" />
 		<method name="getBaseStationLatitude()I" />
@@ -21113,6 +21871,7 @@
 		<method name="expandTemplate(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/CharSequence;" />
 		<method name="getCapsMode(Ljava/lang/CharSequence;II)I" since="3" />
 		<method name="getChars(Ljava/lang/CharSequence;II[CI)V" />
+		<method name="getLayoutDirectionFromLocale(Ljava/util/Locale;)I" since="17" />
 		<method name="getOffsetAfter(Ljava/lang/CharSequence;I)I" />
 		<method name="getOffsetBefore(Ljava/lang/CharSequence;I)I" />
 		<method name="getReverse(Ljava/lang/CharSequence;II)Ljava/lang/CharSequence;" />
@@ -21199,6 +21958,7 @@
 		<field name="MONTH" />
 		<field name="QUOTE" />
 		<field name="SECONDS" />
+		<field name="STANDALONE_MONTH" since="17" />
 		<field name="TIME_ZONE" />
 		<field name="YEAR" />
 	</class>
@@ -21688,6 +22448,13 @@
 		<implements name="android/text/style/LineHeightSpan" />
 		<method name="chooseHeight(Ljava/lang/CharSequence;IIIILandroid/graphics/Paint$FontMetricsInt;Landroid/text/TextPaint;)V" />
 	</class>
+	<class name="android/text/style/LocaleSpan" since="17">
+		<extends name="android/text/style/MetricAffectingSpan" />
+		<implements name="android/text/ParcelableSpan" />
+		<method name="&lt;init>(Landroid/os/Parcel;)V" />
+		<method name="&lt;init>(Ljava/util/Locale;)V" />
+		<method name="getLocale()Ljava/util/Locale;" />
+	</class>
 	<class name="android/text/style/MaskFilterSpan" since="1">
 		<extends name="android/text/style/CharacterStyle" />
 		<implements name="android/text/style/UpdateAppearance" since="3" />
@@ -21901,6 +22668,17 @@
 		<method name="&lt;init>(Ljava/lang/String;)V" />
 		<method name="&lt;init>(Ljava/lang/String;Ljava/lang/Throwable;)V" since="11" />
 	</class>
+	<class name="android/util/AtomicFile" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>(Ljava/io/File;)V" />
+		<method name="delete()V" />
+		<method name="failWrite(Ljava/io/FileOutputStream;)V" />
+		<method name="finishWrite(Ljava/io/FileOutputStream;)V" />
+		<method name="getBaseFile()Ljava/io/File;" />
+		<method name="openRead()Ljava/io/FileInputStream;" />
+		<method name="readFully()[B" />
+		<method name="startWrite()Ljava/io/FileOutputStream;" />
+	</class>
 	<class name="android/util/AttributeSet" since="1">
 		<extends name="java/lang/Object" />
 		<method name="getAttributeBooleanValue(IZ)Z" />
@@ -21972,6 +22750,7 @@
 	<class name="android/util/DisplayMetrics" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
+		<method name="equals(Landroid/util/DisplayMetrics;)Z" since="17" />
 		<method name="setTo(Landroid/util/DisplayMetrics;)V" />
 		<method name="setToDefaults()V" />
 		<field name="DENSITY_DEFAULT" since="4" />
@@ -22027,7 +22806,10 @@
 		<method name="&lt;init>()V" />
 		<method name="ceil(F)F" />
 		<method name="cos(F)F" />
+		<method name="exp(F)F" since="17" />
 		<method name="floor(F)F" />
+		<method name="hypot(FF)F" since="17" />
+		<method name="pow(FF)F" since="17" />
 		<method name="sin(F)F" />
 		<method name="sqrt(F)F" />
 	</class>
@@ -22087,6 +22869,11 @@
 		<method name="value(Ljava/lang/String;)Landroid/util/JsonWriter;" />
 		<method name="value(Z)Landroid/util/JsonWriter;" />
 	</class>
+	<class name="android/util/LocaleUtil" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="getLayoutDirectionFromLocale(Ljava/util/Locale;)I" />
+	</class>
 	<class name="android/util/Log" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -22158,6 +22945,7 @@
 		<method name="size()I" />
 		<method name="sizeOf(Ljava/lang/Object;Ljava/lang/Object;)I" />
 		<method name="snapshot()Ljava/util/Map;" />
+		<method name="trimToSize(I)V" since="17" />
 	</class>
 	<class name="android/util/MalformedJsonException" since="11">
 		<extends name="java/io/IOException" />
@@ -22231,6 +23019,15 @@
 		<method name="of(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Landroid/util/Property;" />
 		<method name="set(Ljava/lang/Object;Ljava/lang/Object;)V" />
 	</class>
+	<class name="android/util/PropertyValueModel" since="17">
+		<extends name="android/util/ValueModel" />
+		<method name="&lt;init>()V" />
+		<method name="getHost()Ljava/lang/Object;" />
+		<method name="getProperty()Landroid/util/Property;" />
+		<method name="of(Ljava/lang/Object;Landroid/util/Property;)Landroid/util/PropertyValueModel;" />
+		<method name="of(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)Landroid/util/PropertyValueModel;" />
+		<method name="of(Ljava/lang/Object;Ljava/lang/String;)Landroid/util/PropertyValueModel;" />
+	</class>
 	<class name="android/util/SparseArray" since="1">
 		<extends name="java/lang/Object" />
 		<implements name="java/lang/Cloneable" since="14" />
@@ -22385,6 +23182,14 @@
 		<field name="string" />
 		<field name="type" />
 	</class>
+	<class name="android/util/ValueModel" since="17">
+		<extends name="java/lang/Object" />
+		<method name="&lt;init>()V" />
+		<method name="get()Ljava/lang/Object;" />
+		<method name="getType()Ljava/lang/Class;" />
+		<method name="set(Ljava/lang/Object;)V" />
+		<field name="EMPTY" />
+	</class>
 	<class name="android/util/Xml" since="1">
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
@@ -22494,6 +23299,7 @@
 		<extends name="android/content/ContextWrapper" />
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(Landroid/content/Context;I)V" />
+		<method name="applyOverrideConfiguration(Landroid/content/res/Configuration;)V" since="17" />
 		<method name="onApplyThemeResource(Landroid/content/res/Resources$Theme;IZ)V" />
 	</class>
 	<class name="android/view/Display" since="1">
@@ -22501,16 +23307,23 @@
 		<method name="&lt;init>()V" />
 		<method name="getCurrentSizeRange(Landroid/graphics/Point;Landroid/graphics/Point;)V" since="16" />
 		<method name="getDisplayId()I" />
+		<method name="getFlags()I" since="17" />
 		<method name="getHeight()I" />
 		<method name="getMetrics(Landroid/util/DisplayMetrics;)V" />
+		<method name="getName()Ljava/lang/String;" since="17" />
 		<method name="getOrientation()I" />
 		<method name="getPixelFormat()I" />
+		<method name="getRealMetrics(Landroid/util/DisplayMetrics;)V" since="17" />
+		<method name="getRealSize(Landroid/graphics/Point;)V" since="17" />
 		<method name="getRectSize(Landroid/graphics/Rect;)V" since="13" />
 		<method name="getRefreshRate()F" />
 		<method name="getRotation()I" since="8" />
 		<method name="getSize(Landroid/graphics/Point;)V" since="13" />
 		<method name="getWidth()I" />
+		<method name="isValid()Z" since="17" />
 		<field name="DEFAULT_DISPLAY" />
+		<field name="FLAG_SECURE" since="17" />
+		<field name="FLAG_SUPPORTS_PROTECTED_BUFFERS" since="17" />
 	</class>
 	<class name="android/view/DragEvent" since="11">
 		<extends name="java/lang/Object" />
@@ -22576,8 +23389,11 @@
 		<extends name="java/lang/Object" />
 		<method name="&lt;init>()V" />
 		<method name="apply(IIILandroid/graphics/Rect;IILandroid/graphics/Rect;)V" />
+		<method name="apply(IIILandroid/graphics/Rect;IILandroid/graphics/Rect;I)V" since="17" />
 		<method name="apply(IIILandroid/graphics/Rect;Landroid/graphics/Rect;)V" />
+		<method name="apply(IIILandroid/graphics/Rect;Landroid/graphics/Rect;I)V" since="17" />
 		<method name="applyDisplay(ILandroid/graphics/Rect;Landroid/graphics/Rect;)V" since="3" />
+		<method name="applyDisplay(ILandroid/graphics/Rect;Landroid/graphics/Rect;I)V" since="17" />
 		<method name="getAbsoluteGravity(II)I" since="14" />
 		<method name="isHorizontal(I)Z" />
 		<method name="isVertical(I)Z" />
@@ -23581,6 +24397,7 @@
 		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" />
 		<method name="gatherTransparentRegion(Landroid/graphics/Region;)Z" />
 		<method name="getHolder()Landroid/view/SurfaceHolder;" />
+		<method name="setSecure(Z)V" since="17" />
 		<method name="setZOrderMediaOverlay(Z)V" since="5" />
 		<method name="setZOrderOnTop(Z)V" since="5" />
 	</class>
@@ -23710,6 +24527,7 @@
 		<method name="fitsSystemWindows()Z" since="14" />
 		<method name="focusSearch(I)Landroid/view/View;" />
 		<method name="forceLayout()V" />
+		<method name="generateViewId()I" since="17" />
 		<method name="getAccessibilityNodeProvider()Landroid/view/accessibility/AccessibilityNodeProvider;" since="16" />
 		<method name="getAlpha()F" since="11" />
 		<method name="getAnimation()Landroid/view/animation/Animation;" />
@@ -23724,6 +24542,7 @@
 		<method name="getContext()Landroid/content/Context;" />
 		<method name="getContextMenuInfo()Landroid/view/ContextMenu$ContextMenuInfo;" />
 		<method name="getDefaultSize(II)I" />
+		<method name="getDisplay()Landroid/view/Display;" since="17" />
 		<method name="getDrawableState()[I" />
 		<method name="getDrawingCache()Landroid/graphics/Bitmap;" />
 		<method name="getDrawingCache(Z)Landroid/graphics/Bitmap;" since="4" />
@@ -23746,7 +24565,9 @@
 		<method name="getImportantForAccessibility()I" since="16" />
 		<method name="getKeepScreenOn()Z" />
 		<method name="getKeyDispatcherState()Landroid/view/KeyEvent$DispatcherState;" since="5" />
+		<method name="getLabelFor()I" since="17" />
 		<method name="getLayerType()I" since="11" />
+		<method name="getLayoutDirection()I" since="17" />
 		<method name="getLayoutParams()Landroid/view/ViewGroup$LayoutParams;" />
 		<method name="getLeft()I" />
 		<method name="getLeftFadingEdgeStrength()F" />
@@ -23770,8 +24591,10 @@
 		<method name="getOnFocusChangeListener()Landroid/view/View$OnFocusChangeListener;" />
 		<method name="getOverScrollMode()I" since="9" />
 		<method name="getPaddingBottom()I" />
+		<method name="getPaddingEnd()I" since="17" />
 		<method name="getPaddingLeft()I" />
 		<method name="getPaddingRight()I" />
+		<method name="getPaddingStart()I" since="17" />
 		<method name="getPaddingTop()I" />
 		<method name="getParent()Landroid/view/ViewParent;" />
 		<method name="getParentForAccessibility()Landroid/view/ViewParent;" since="16" />
@@ -23799,6 +24622,8 @@
 		<method name="getSystemUiVisibility()I" since="11" />
 		<method name="getTag()Ljava/lang/Object;" />
 		<method name="getTag(I)Ljava/lang/Object;" since="4" />
+		<method name="getTextAlignment()I" since="17" />
+		<method name="getTextDirection()I" since="17" />
 		<method name="getTop()I" />
 		<method name="getTopFadingEdgeStrength()F" />
 		<method name="getTopPaddingOffset()I" since="2" />
@@ -23851,6 +24676,7 @@
 		<method name="isLongClickable()Z" />
 		<method name="isOpaque()Z" since="7" />
 		<method name="isPaddingOffsetRequired()Z" since="2" />
+		<method name="isPaddingRelative()Z" since="17" />
 		<method name="isPressed()Z" />
 		<method name="isSaveEnabled()Z" />
 		<method name="isSaveFromParentEnabled()Z" since="11" />
@@ -23896,6 +24722,7 @@
 		<method name="onOverScrolled(IIZZ)V" since="9" />
 		<method name="onPopulateAccessibilityEvent(Landroid/view/accessibility/AccessibilityEvent;)V" since="14" />
 		<method name="onRestoreInstanceState(Landroid/os/Parcelable;)V" />
+		<method name="onRtlPropertiesChanged(I)V" since="17" />
 		<method name="onSaveInstanceState()Landroid/os/Parcelable;" />
 		<method name="onScreenStateChanged(I)V" since="16" />
 		<method name="onScrollChanged(IIII)V" />
@@ -23973,7 +24800,10 @@
 		<method name="setId(I)V" />
 		<method name="setImportantForAccessibility(I)V" since="16" />
 		<method name="setKeepScreenOn(Z)V" />
+		<method name="setLabelFor(I)V" since="17" />
+		<method name="setLayerPaint(Landroid/graphics/Paint;)V" since="17" />
 		<method name="setLayerType(ILandroid/graphics/Paint;)V" since="11" />
+		<method name="setLayoutDirection(I)V" since="17" />
 		<method name="setLayoutParams(Landroid/view/ViewGroup$LayoutParams;)V" />
 		<method name="setLeft(I)V" since="11" />
 		<method name="setLongClickable(Z)V" />
@@ -23997,6 +24827,7 @@
 		<method name="setOnTouchListener(Landroid/view/View$OnTouchListener;)V" />
 		<method name="setOverScrollMode(I)V" since="9" />
 		<method name="setPadding(IIII)V" />
+		<method name="setPaddingRelative(IIII)V" since="17" />
 		<method name="setPivotX(F)V" since="11" />
 		<method name="setPivotY(F)V" since="11" />
 		<method name="setPressed(Z)V" />
@@ -24021,6 +24852,8 @@
 		<method name="setSystemUiVisibility(I)V" since="11" />
 		<method name="setTag(ILjava/lang/Object;)V" since="4" />
 		<method name="setTag(Ljava/lang/Object;)V" />
+		<method name="setTextAlignment(I)V" since="17" />
+		<method name="setTextDirection(I)V" since="17" />
 		<method name="setTop(I)V" since="11" />
 		<method name="setTouchDelegate(Landroid/view/TouchDelegate;)V" />
 		<method name="setTranslationX(F)V" since="11" />
@@ -24078,6 +24911,10 @@
 		<field name="LAYER_TYPE_HARDWARE" since="11" />
 		<field name="LAYER_TYPE_NONE" since="11" />
 		<field name="LAYER_TYPE_SOFTWARE" since="11" />
+		<field name="LAYOUT_DIRECTION_INHERIT" since="17" />
+		<field name="LAYOUT_DIRECTION_LOCALE" since="17" />
+		<field name="LAYOUT_DIRECTION_LTR" since="17" />
+		<field name="LAYOUT_DIRECTION_RTL" since="17" />
 		<field name="MEASURED_HEIGHT_STATE_SHIFT" since="11" />
 		<field name="MEASURED_SIZE_MASK" since="11" />
 		<field name="MEASURED_STATE_MASK" since="11" />
@@ -24128,8 +24965,20 @@
 		<field name="SYSTEM_UI_FLAG_LOW_PROFILE" since="14" />
 		<field name="SYSTEM_UI_FLAG_VISIBLE" since="14" />
 		<field name="SYSTEM_UI_LAYOUT_FLAGS" since="16" />
+		<field name="TEXT_ALIGNMENT_CENTER" since="17" />
+		<field name="TEXT_ALIGNMENT_GRAVITY" since="17" />
 		<field name="TEXT_ALIGNMENT_INHERIT" since="16" />
 		<field name="TEXT_ALIGNMENT_RESOLVED_DEFAULT" since="16" />
+		<field name="TEXT_ALIGNMENT_TEXT_END" since="17" />
+		<field name="TEXT_ALIGNMENT_TEXT_START" since="17" />
+		<field name="TEXT_ALIGNMENT_VIEW_END" since="17" />
+		<field name="TEXT_ALIGNMENT_VIEW_START" since="17" />
+		<field name="TEXT_DIRECTION_ANY_RTL" since="17" />
+		<field name="TEXT_DIRECTION_FIRST_STRONG" since="17" />
+		<field name="TEXT_DIRECTION_INHERIT" since="17" />
+		<field name="TEXT_DIRECTION_LOCALE" since="17" />
+		<field name="TEXT_DIRECTION_LTR" since="17" />
+		<field name="TEXT_DIRECTION_RTL" since="17" />
 		<field name="TRANSLATION_X" since="14" />
 		<field name="TRANSLATION_Y" since="14" />
 		<field name="VIEW_LOG_TAG" />
@@ -24435,6 +25284,7 @@
 		<method name="&lt;init>(II)V" />
 		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" />
 		<method name="&lt;init>(Landroid/view/ViewGroup$LayoutParams;)V" />
+		<method name="resolveLayoutDirection(I)V" since="17" />
 		<method name="setBaseAttributes(Landroid/content/res/TypedArray;II)V" />
 		<field name="FILL_PARENT" />
 		<field name="MATCH_PARENT" since="8" />
@@ -24449,6 +25299,13 @@
 		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" />
 		<method name="&lt;init>(Landroid/view/ViewGroup$LayoutParams;)V" />
 		<method name="&lt;init>(Landroid/view/ViewGroup$MarginLayoutParams;)V" />
+		<method name="getLayoutDirection()I" since="17" />
+		<method name="getMarginEnd()I" since="17" />
+		<method name="getMarginStart()I" since="17" />
+		<method name="isMarginRelative()Z" since="17" />
+		<method name="setLayoutDirection(I)V" since="17" />
+		<method name="setMarginEnd(I)V" since="17" />
+		<method name="setMarginStart(I)V" since="17" />
 		<method name="setMargins(IIII)V" />
 		<field name="bottomMargin" />
 		<field name="leftMargin" />
@@ -24727,6 +25584,11 @@
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(Ljava/lang/String;)V" />
 	</class>
+	<class name="android/view/WindowManager$InvalidDisplayException" since="17">
+		<extends name="java/lang/RuntimeException" />
+		<method name="&lt;init>()V" />
+		<method name="&lt;init>(Ljava/lang/String;)V" />
+	</class>
 	<class name="android/view/WindowManager$LayoutParams" since="1">
 		<extends name="android/view/ViewGroup$LayoutParams" />
 		<implements name="android/os/Parcelable" />
@@ -24906,9 +25768,13 @@
 		<field name="MAX_TEXT_LENGTH" />
 		<field name="TYPES_ALL_MASK" />
 		<field name="TYPE_ANNOUNCEMENT" since="16" />
+		<field name="TYPE_GESTURE_DETECTION_END" since="17" />
+		<field name="TYPE_GESTURE_DETECTION_START" since="17" />
 		<field name="TYPE_NOTIFICATION_STATE_CHANGED" />
 		<field name="TYPE_TOUCH_EXPLORATION_GESTURE_END" since="14" />
 		<field name="TYPE_TOUCH_EXPLORATION_GESTURE_START" since="14" />
+		<field name="TYPE_TOUCH_INTERACTION_END" since="17" />
+		<field name="TYPE_TOUCH_INTERACTION_START" since="17" />
 		<field name="TYPE_VIEW_ACCESSIBILITY_FOCUSED" since="16" />
 		<field name="TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED" since="16" />
 		<field name="TYPE_VIEW_CLICKED" />
@@ -24963,6 +25829,8 @@
 		<method name="getChildCount()I" />
 		<method name="getClassName()Ljava/lang/CharSequence;" />
 		<method name="getContentDescription()Ljava/lang/CharSequence;" />
+		<method name="getLabelFor()Landroid/view/accessibility/AccessibilityNodeInfo;" since="17" />
+		<method name="getLabeledBy()Landroid/view/accessibility/AccessibilityNodeInfo;" since="17" />
 		<method name="getMovementGranularities()I" since="16" />
 		<method name="getPackageName()Ljava/lang/CharSequence;" />
 		<method name="getParent()Landroid/view/accessibility/AccessibilityNodeInfo;" />
@@ -24998,6 +25866,10 @@
 		<method name="setEnabled(Z)V" />
 		<method name="setFocusable(Z)V" />
 		<method name="setFocused(Z)V" />
+		<method name="setLabelFor(Landroid/view/View;)V" since="17" />
+		<method name="setLabelFor(Landroid/view/View;I)V" since="17" />
+		<method name="setLabeledBy(Landroid/view/View;)V" since="17" />
+		<method name="setLabeledBy(Landroid/view/View;I)V" since="17" />
 		<method name="setLongClickable(Z)V" />
 		<method name="setMovementGranularities(I)V" since="16" />
 		<method name="setPackageName(Ljava/lang/CharSequence;)V" />
@@ -25590,6 +26462,7 @@
 	<class name="android/view/inputmethod/InputMethodSession" since="3">
 		<extends name="java/lang/Object" />
 		<method name="appPrivateCommand(Ljava/lang/String;Landroid/os/Bundle;)V" />
+		<method name="dispatchGenericMotionEvent(ILandroid/view/MotionEvent;Landroid/view/inputmethod/InputMethodSession$EventCallback;)V" since="17" />
 		<method name="dispatchKeyEvent(ILandroid/view/KeyEvent;Landroid/view/inputmethod/InputMethodSession$EventCallback;)V" />
 		<method name="dispatchTrackballEvent(ILandroid/view/MotionEvent;Landroid/view/inputmethod/InputMethodSession$EventCallback;)V" />
 		<method name="displayCompletions([Landroid/view/inputmethod/CompletionInfo;)V" />
@@ -25609,6 +26482,7 @@
 		<implements name="android/os/Parcelable" />
 		<method name="&lt;init>()V" />
 		<method name="&lt;init>(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)V" since="14" />
+		<method name="&lt;init>(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZI)V" since="17" />
 		<method name="containsExtraValueKey(Ljava/lang/String;)Z" since="12" />
 		<method name="getDisplayName(Landroid/content/Context;Ljava/lang/String;Landroid/content/pm/ApplicationInfo;)Ljava/lang/CharSequence;" since="14" />
 		<method name="getExtraValue()Ljava/lang/String;" />
@@ -25853,6 +26727,10 @@
 		<method name="proceed(Ljava/lang/String;Ljava/lang/String;)V" />
 		<method name="useHttpAuthUsernamePassword()Z" />
 	</class>
+	<class name="android/webkit/JavascriptInterface" since="17">
+		<extends name="java/lang/Object" />
+		<implements name="java/lang/annotation/Annotation" />
+	</class>
 	<class name="android/webkit/JsPromptResult" since="1">
 		<extends name="android/webkit/JsResult" />
 		<method name="&lt;init>()V" />
@@ -26059,6 +26937,7 @@
 		<method name="getDefaultFixedFontSize()I" />
 		<method name="getDefaultFontSize()I" />
 		<method name="getDefaultTextEncodingName()Ljava/lang/String;" />
+		<method name="getDefaultUserAgent(Landroid/content/Context;)Ljava/lang/String;" since="17" />
 		<method name="getDefaultZoom()Landroid/webkit/WebSettings$ZoomDensity;" since="7" />
 		<method name="getDisplayZoomControls()Z" since="11" />
 		<method name="getDomStorageEnabled()Z" since="7" />
@@ -26070,6 +26949,7 @@
 		<method name="getLightTouchEnabled()Z" />
 		<method name="getLoadWithOverviewMode()Z" since="7" />
 		<method name="getLoadsImagesAutomatically()Z" />
+		<method name="getMediaPlaybackRequiresUserGesture()Z" since="17" />
 		<method name="getMinimumFontSize()I" />
 		<method name="getMinimumLogicalFontSize()I" />
 		<method name="getNavDump()Z" />
@@ -26119,6 +26999,7 @@
 		<method name="setLightTouchEnabled(Z)V" />
 		<method name="setLoadWithOverviewMode(Z)V" since="7" />
 		<method name="setLoadsImagesAutomatically(Z)V" />
+		<method name="setMediaPlaybackRequiresUserGesture(Z)V" since="17" />
 		<method name="setMinimumFontSize(I)V" />
 		<method name="setMinimumLogicalFontSize(I)V" />
 		<method name="setNavDump(Z)V" />
@@ -26739,12 +27620,18 @@
 		<method name="setDropDownVerticalOffset(I)V" since="5" />
 		<method name="setDropDownWidth(I)V" since="3" />
 		<method name="setListSelection(I)V" since="3" />
+		<method name="setOnDismissListener(Landroid/widget/AutoCompleteTextView$OnDismissListener;)V" since="17" />
 		<method name="setOnItemClickListener(Landroid/widget/AdapterView$OnItemClickListener;)V" />
 		<method name="setOnItemSelectedListener(Landroid/widget/AdapterView$OnItemSelectedListener;)V" />
+		<method name="setText(Ljava/lang/CharSequence;Z)V" since="17" />
 		<method name="setThreshold(I)V" />
 		<method name="setValidator(Landroid/widget/AutoCompleteTextView$Validator;)V" />
 		<method name="showDropDown()V" />
 	</class>
+	<class name="android/widget/AutoCompleteTextView$OnDismissListener" since="17">
+		<extends name="java/lang/Object" />
+		<method name="onDismiss()V" />
+	</class>
 	<class name="android/widget/AutoCompleteTextView$Validator" since="1">
 		<extends name="java/lang/Object" />
 		<method name="fixText(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;" />
@@ -27774,19 +28661,25 @@
 		<field name="ABOVE" />
 		<field name="ALIGN_BASELINE" />
 		<field name="ALIGN_BOTTOM" />
+		<field name="ALIGN_END" since="17" />
 		<field name="ALIGN_LEFT" />
 		<field name="ALIGN_PARENT_BOTTOM" />
+		<field name="ALIGN_PARENT_END" since="17" />
 		<field name="ALIGN_PARENT_LEFT" />
 		<field name="ALIGN_PARENT_RIGHT" />
+		<field name="ALIGN_PARENT_START" since="17" />
 		<field name="ALIGN_PARENT_TOP" />
 		<field name="ALIGN_RIGHT" />
+		<field name="ALIGN_START" since="17" />
 		<field name="ALIGN_TOP" />
 		<field name="BELOW" />
 		<field name="CENTER_HORIZONTAL" />
 		<field name="CENTER_IN_PARENT" />
 		<field name="CENTER_VERTICAL" />
+		<field name="END_OF" since="17" />
 		<field name="LEFT_OF" />
 		<field name="RIGHT_OF" />
+		<field name="START_OF" since="17" />
 		<field name="TRUE" />
 	</class>
 	<class name="android/widget/RelativeLayout$LayoutParams" since="1">
@@ -27799,6 +28692,7 @@
 		<method name="addRule(II)V" />
 		<method name="debug(Ljava/lang/String;)Ljava/lang/String;" />
 		<method name="getRules()[I" />
+		<method name="removeRule(I)V" since="17" />
 		<field name="alignWithParent" />
 	</class>
 	<class name="android/widget/RemoteViews" since="1">
@@ -27832,6 +28726,7 @@
 		<method name="setImageViewUri(ILandroid/net/Uri;)V" />
 		<method name="setInt(ILjava/lang/String;I)V" since="3" />
 		<method name="setIntent(ILjava/lang/String;Landroid/content/Intent;)V" since="11" />
+		<method name="setLabelFor(II)V" since="17" />
 		<method name="setLong(ILjava/lang/String;J)V" since="3" />
 		<method name="setOnClickFillInIntent(ILandroid/content/Intent;)V" since="11" />
 		<method name="setOnClickPendingIntent(ILandroid/app/PendingIntent;)V" since="3" />
@@ -28281,6 +29176,21 @@
 		<field name="column" />
 		<field name="span" />
 	</class>
+	<class name="android/widget/TextClock" since="17">
+		<extends name="android/widget/TextView" />
+		<method name="&lt;init>(Landroid/content/Context;)V" />
+		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" />
+		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V" />
+		<method name="getFormat12Hour()Ljava/lang/CharSequence;" />
+		<method name="getFormat24Hour()Ljava/lang/CharSequence;" />
+		<method name="getTimeZone()Ljava/lang/String;" />
+		<method name="is24HourModeEnabled()Z" />
+		<method name="setFormat12Hour(Ljava/lang/CharSequence;)V" />
+		<method name="setFormat24Hour(Ljava/lang/CharSequence;)V" />
+		<method name="setTimeZone(Ljava/lang/String;)V" />
+		<field name="DEFAULT_FORMAT_12_HOUR" />
+		<field name="DEFAULT_FORMAT_24_HOUR" />
+	</class>
 	<class name="android/widget/TextSwitcher" since="1">
 		<extends name="android/widget/ViewSwitcher" />
 		<method name="&lt;init>(Landroid/content/Context;)V" />
@@ -28307,9 +29217,12 @@
 		<method name="getAutoLinkMask()I" />
 		<method name="getCompoundDrawablePadding()I" />
 		<method name="getCompoundDrawables()[Landroid/graphics/drawable/Drawable;" />
+		<method name="getCompoundDrawablesRelative()[Landroid/graphics/drawable/Drawable;" since="17" />
 		<method name="getCompoundPaddingBottom()I" />
+		<method name="getCompoundPaddingEnd()I" since="17" />
 		<method name="getCompoundPaddingLeft()I" />
 		<method name="getCompoundPaddingRight()I" />
+		<method name="getCompoundPaddingStart()I" since="17" />
 		<method name="getCompoundPaddingTop()I" />
 		<method name="getCurrentHintTextColor()I" />
 		<method name="getCurrentTextColor()I" />
@@ -28366,11 +29279,14 @@
 		<method name="getTextColor(Landroid/content/Context;Landroid/content/res/TypedArray;I)I" />
 		<method name="getTextColors()Landroid/content/res/ColorStateList;" />
 		<method name="getTextColors(Landroid/content/Context;Landroid/content/res/TypedArray;)Landroid/content/res/ColorStateList;" />
+		<method name="getTextLocale()Ljava/util/Locale;" since="17" />
 		<method name="getTextScaleX()F" />
 		<method name="getTextSize()F" />
 		<method name="getTotalPaddingBottom()I" />
+		<method name="getTotalPaddingEnd()I" since="17" />
 		<method name="getTotalPaddingLeft()I" />
 		<method name="getTotalPaddingRight()I" />
+		<method name="getTotalPaddingStart()I" since="17" />
 		<method name="getTotalPaddingTop()I" />
 		<method name="getTransformationMethod()Landroid/text/method/TransformationMethod;" />
 		<method name="getTypeface()Landroid/graphics/Typeface;" />
@@ -28400,6 +29316,9 @@
 		<method name="setAutoLinkMask(I)V" />
 		<method name="setCompoundDrawablePadding(I)V" />
 		<method name="setCompoundDrawables(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V" />
+		<method name="setCompoundDrawablesRelative(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V" since="17" />
+		<method name="setCompoundDrawablesRelativeWithIntrinsicBounds(IIII)V" since="17" />
+		<method name="setCompoundDrawablesRelativeWithIntrinsicBounds(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V" since="17" />
 		<method name="setCompoundDrawablesWithIntrinsicBounds(IIII)V" since="3" />
 		<method name="setCompoundDrawablesWithIntrinsicBounds(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V" />
 		<method name="setCursorVisible(Z)V" />
@@ -28464,6 +29383,7 @@
 		<method name="setTextIsSelectable(Z)V" since="11" />
 		<method name="setTextKeepState(Ljava/lang/CharSequence;)V" />
 		<method name="setTextKeepState(Ljava/lang/CharSequence;Landroid/widget/TextView$BufferType;)V" />
+		<method name="setTextLocale(Ljava/util/Locale;)V" since="17" />
 		<method name="setTextScaleX(F)V" />
 		<method name="setTextSize(F)V" />
 		<method name="setTextSize(IF)V" />
@@ -28547,6 +29467,11 @@
 		<method name="getText1()Landroid/widget/TextView;" />
 		<method name="getText2()Landroid/widget/TextView;" />
 	</class>
+	<class name="android/widget/ValueEditor" since="17">
+		<extends name="java/lang/Object" />
+		<method name="getValueModel()Landroid/util/ValueModel;" />
+		<method name="setValueModel(Landroid/util/ValueModel;)V" />
+	</class>
 	<class name="android/widget/VideoView" since="1">
 		<extends name="android/view/SurfaceView" />
 		<implements name="android/widget/MediaController$MediaPlayerControl" />
@@ -28558,6 +29483,7 @@
 		<method name="setMediaController(Landroid/widget/MediaController;)V" />
 		<method name="setOnCompletionListener(Landroid/media/MediaPlayer$OnCompletionListener;)V" />
 		<method name="setOnErrorListener(Landroid/media/MediaPlayer$OnErrorListener;)V" />
+		<method name="setOnInfoListener(Landroid/media/MediaPlayer$OnInfoListener;)V" since="17" />
 		<method name="setOnPreparedListener(Landroid/media/MediaPlayer$OnPreparedListener;)V" />
 		<method name="setVideoPath(Ljava/lang/String;)V" />
 		<method name="setVideoURI(Landroid/net/Uri;)V" />
@@ -28568,6 +29494,7 @@
 		<extends name="android/widget/FrameLayout" />
 		<method name="&lt;init>(Landroid/content/Context;)V" />
 		<method name="&lt;init>(Landroid/content/Context;Landroid/util/AttributeSet;)V" />
+		<method name="getAnimateFirstView()Z" since="17" />
 		<method name="getCurrentView()Landroid/view/View;" />
 		<method name="getDisplayedChild()I" />
 		<method name="getInAnimation()Landroid/view/animation/Animation;" />
diff --git a/sdk/doc_source.prop_template b/sdk/doc_source.prop_template
index 3d4fac5..d3cdfd5 100644
--- a/sdk/doc_source.prop_template
+++ b/sdk/doc_source.prop_template
@@ -1,4 +1,4 @@
 Pkg.UserSrc=false
-Pkg.Revision=3
+Pkg.Revision=1
 AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
 AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
diff --git a/sdk/plat_tools_source.properties b/sdk/plat_tools_source.properties
index ea0d089..37153b5 100644
--- a/sdk/plat_tools_source.properties
+++ b/sdk/plat_tools_source.properties
@@ -1,2 +1,2 @@
 Pkg.UserSrc=false
-Pkg.Revision=15
\ No newline at end of file
+Pkg.Revision=16.0.2
\ No newline at end of file
diff --git a/sdk/source_source.prop_template b/sdk/source_source.prop_template
index 3d4fac5..d3cdfd5 100644
--- a/sdk/source_source.prop_template
+++ b/sdk/source_source.prop_template
@@ -1,4 +1,4 @@
 Pkg.UserSrc=false
-Pkg.Revision=3
+Pkg.Revision=1
 AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
 AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
diff --git a/sdk/support_source.properties b/sdk/support_source.properties
index 47b0e05..fb611ec 100644
--- a/sdk/support_source.properties
+++ b/sdk/support_source.properties
@@ -1,5 +1,5 @@
 Pkg.UserSrc=false
-Pkg.Revision=10
+Pkg.Revision=11
 Extra.Vendor=android
 Extra.VendorId=android
 Extra.VendorDisplay=Android
diff --git a/sdk_overlay/frameworks/base/core/res/res/values/config.xml b/sdk_overlay/frameworks/base/core/res/res/values/config.xml
index fdeb8bd..7c5b1ce 100644
--- a/sdk_overlay/frameworks/base/core/res/res/values/config.xml
+++ b/sdk_overlay/frameworks/base/core/res/res/values/config.xml
@@ -20,8 +20,11 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- Package name providing geocoder API support. -->
-    <string name="config_geocodeProviderPackageName" translatable="false">com.google.android.location</string>
+    <!-- Package name providing location API support. -->
+    <string-array name="config_locationProviderPackageNames" translatable="false">
+        <item>com.google.android.location</item>
+        <item>com.android.location.fused</item>
+    </string-array>
 
     <bool name="config_voice_capable">true</bool>
 </resources>
diff --git a/testrunner/.gitignore b/testrunner/.gitignore
index 0d20b64..b3421b0 100644
--- a/testrunner/.gitignore
+++ b/testrunner/.gitignore
@@ -1 +1,2 @@
 *.pyc
+.*
diff --git a/testrunner/adb_interface.py b/testrunner/adb_interface.py
index ddc50d5..1f1e3c6 100755
--- a/testrunner/adb_interface.py
+++ b/testrunner/adb_interface.py
@@ -131,6 +131,17 @@
       logger.Log("ADB Pull Failed: Source file %s does not exist." % src)
       return False
 
+  def Install(self, apk_path):
+    """Installs apk on device.
+
+    Args:
+      apk_path: file path to apk file on host
+
+    Returns:
+      output of install command
+    """
+    return self.SendCommand("install -r %s" % apk_path)
+
   def DoesFileExist(self, src):
     """Checks if the given path exists on device target.
 
@@ -325,29 +336,16 @@
       raise errors.WaitForResponseTimedOutError(
           "Package manager did not respond after %s seconds" % wait_time)
 
-  def WaitForInstrumentation(self, package_name, runner_name, wait_time=120):
-    """Waits for given instrumentation to be present on device
-
-    Args:
-      wait_time: time in seconds to wait
-
-    Raises:
-      WaitForResponseTimedOutError if wait_time elapses and instrumentation
-      still not present.
-    """
+  def IsInstrumentationInstalled(self, package_name, runner_name):
+    """Checks if instrumentation is present on device."""
     instrumentation_path = "%s/%s" % (package_name, runner_name)
-    logger.Log("Waiting for instrumentation to be present")
-    # Query the package manager
+    command = "pm list instrumentation | grep %s" % instrumentation_path
     try:
-      command = "pm list instrumentation | grep %s" % instrumentation_path
-      self._WaitForShellCommandContents(command, "instrumentation:", wait_time,
-                                        raise_abort=False)
-    except errors.WaitForResponseTimedOutError :
-      logger.Log(
-          "Could not find instrumentation %s on device. Does the "
-          "instrumentation in test's AndroidManifest.xml match definition"
-          "in test_defs.xml?" % instrumentation_path)
-      raise
+      output = self.SendShellCommand(command)
+      return output.startswith("instrumentation:")
+    except errors.AbortError:
+      # command can return error code on failure
+      return False
 
   def WaitForProcess(self, name, wait_time=120):
     """Wait until a process is running on the device.
diff --git a/testrunner/android_build.py b/testrunner/android_build.py
index 584ef52..a10d43b 100644
--- a/testrunner/android_build.py
+++ b/testrunner/android_build.py
@@ -42,7 +42,8 @@
   # TODO: does this need to be reimplemented to be like gettop() in envsetup.sh
   root_path = os.getenv("ANDROID_BUILD_TOP")
   if root_path is None:
-    logger.Log("Error: ANDROID_BUILD_TOP not defined. Please run envsetup.sh")
+    logger.Log("Error: ANDROID_BUILD_TOP not defined. Please run "
+               "envsetup.sh and lunch/choosecombo")
     raise errors.AbortError
   return root_path
 
@@ -109,7 +110,8 @@
   """
   path = os.getenv("ANDROID_PRODUCT_OUT")
   if path is None:
-    logger.Log("Error: ANDROID_PRODUCT_OUT not defined. Please run envsetup.sh")
+    logger.Log("Error: ANDROID_PRODUCT_OUT not defined. Please run "
+               "envsetup.sh and lunch/choosecombo")
     raise errors.AbortError
   return path
 
diff --git a/testrunner/android_mk.py b/testrunner/android_mk.py
index 2c265ee..7a7769f 100644
--- a/testrunner/android_mk.py
+++ b/testrunner/android_mk.py
@@ -115,6 +115,10 @@
     """
     return identifier in self._includes
 
+  def IncludesMakefilesUnder(self):
+    """Check if makefile has a 'include makefiles under here' rule"""
+    return self.HasInclude('call all-makefiles-under,$(LOCAL_PATH)')
+
   def HasJavaLibrary(self, library_name):
     """Check if library is specified as a local java library in makefile.
 
@@ -168,4 +172,4 @@
     mk._ParseMK(mk_path)
     return mk
   else:
-    return None
\ No newline at end of file
+    return None
diff --git a/testrunner/coverage.py b/testrunner/coverage.py
deleted file mode 100755
index 4322e26..0000000
--- a/testrunner/coverage.py
+++ /dev/null
@@ -1,336 +0,0 @@
-#!/usr/bin/python2.4
-#
-#
-# Copyright 2008, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Utilities for generating code coverage reports for Android tests."""
-
-# Python imports
-import glob
-import optparse
-import os
-
-# local imports
-import android_build
-import coverage_targets
-import errors
-import logger
-import run_command
-
-
-class CoverageGenerator(object):
-  """Helper utility for obtaining code coverage results on Android.
-
-  Intended to simplify the process of building,running, and generating code
-  coverage results for a pre-defined set of tests and targets
-  """
-
-    # path to EMMA host jar, relative to Android build root
-  _EMMA_JAR = os.path.join("external", "emma", "lib", "emma.jar")
-  _TEST_COVERAGE_EXT = "ec"
-  # root path of generated coverage report files, relative to Android build root
-  _COVERAGE_REPORT_PATH = os.path.join("out", "emma")
-  _TARGET_DEF_FILE = "coverage_targets.xml"
-  _CORE_TARGET_PATH = os.path.join("development", "testrunner",
-                                   _TARGET_DEF_FILE)
-  # vendor glob file path patterns to tests, relative to android
-  # build root
-  _VENDOR_TARGET_PATH = os.path.join("vendor", "*", "tests", "testinfo",
-                                     _TARGET_DEF_FILE)
-
-  # path to root of target build intermediates
-  _TARGET_INTERMEDIATES_BASE_PATH = os.path.join("out", "target", "common",
-                                                 "obj")
-
-  def __init__(self, adb_interface):
-    self._root_path = android_build.GetTop()
-    self._output_root_path = os.path.join(self._root_path,
-                                          self._COVERAGE_REPORT_PATH)
-    self._emma_jar_path = os.path.join(self._root_path, self._EMMA_JAR)
-    self._adb = adb_interface
-    self._targets_manifest = self._ReadTargets()
-
-  def ExtractReport(self, test_suite,
-                    device_coverage_path,
-                    output_path=None,
-                    test_qualifier=None):
-    """Extract runtime coverage data and generate code coverage report.
-
-    Assumes test has just been executed.
-    Args:
-      test_suite: TestSuite to generate coverage data for
-      device_coverage_path: location of coverage file on device
-      output_path: path to place output files in. If None will use
-        <android_root_path>/<_COVERAGE_REPORT_PATH>/<target>/<test[-qualifier]>
-      test_qualifier: designates mode test was run with. e.g size=small.
-        If not None, this will be used to customize output_path as shown above.
-
-    Returns:
-      absolute file path string of generated html report file.
-    """
-    if output_path is None:
-      report_name = test_suite.GetName()
-      if test_qualifier:
-        report_name = report_name + "-" + test_qualifier
-      output_path = os.path.join(self._root_path,
-                                 self._COVERAGE_REPORT_PATH,
-                                 test_suite.GetTargetName(),
-                                 report_name)
-
-    coverage_local_name = "%s.%s" % (report_name,
-                                     self._TEST_COVERAGE_EXT)
-    coverage_local_path = os.path.join(output_path,
-                                       coverage_local_name)
-    if self._adb.Pull(device_coverage_path, coverage_local_path):
-
-      report_path = os.path.join(output_path,
-                                 report_name)
-      target = self._targets_manifest.GetTarget(test_suite.GetTargetName())
-      if target is None:
-        msg = ["Error: test %s references undefined target %s."
-                % (test_suite.GetName(), test_suite.GetTargetName())]
-        msg.append(" Ensure target is defined in %s" % self._TARGET_DEF_FILE)
-        logger.Log("".join(msg))
-      else:
-        return self._GenerateReport(report_path, coverage_local_path, [target],
-                                    do_src=True)
-    return None
-
-  def _GenerateReport(self, report_path, coverage_file_path, targets,
-                      do_src=True):
-    """Generate the code coverage report.
-
-    Args:
-      report_path: absolute file path of output file, without extension
-      coverage_file_path: absolute file path of code coverage result file
-      targets: list of CoverageTargets to use as base for code coverage
-          measurement.
-      do_src: True if generate coverage report with source linked in.
-          Note this will increase size of generated report.
-
-    Returns:
-      absolute file path to generated report file.
-    """
-    input_metadatas = self._GatherMetadatas(targets)
-
-    if do_src:
-      src_arg = self._GatherSrcs(targets)
-    else:
-      src_arg = ""
-
-    report_file = "%s.html" % report_path
-    cmd1 = ("java -cp %s emma report -r html -in %s %s %s " %
-            (self._emma_jar_path, coverage_file_path, input_metadatas, src_arg))
-    cmd2 = "-Dreport.html.out.file=%s" % report_file
-    self._RunCmd(cmd1 + cmd2)
-    return report_file
-
-  def _GatherMetadatas(self, targets):
-    """Builds the emma input metadata argument from provided targets.
-
-    Args:
-      targets: list of CoverageTargets
-
-    Returns:
-      input metadata argument string
-    """
-    input_metadatas = ""
-    for target in targets:
-      input_metadata = os.path.join(self._GetBuildIntermediatePath(target),
-                                    "coverage.em")
-      input_metadatas += " -in %s" % input_metadata
-    return input_metadatas
-
-  def _GetBuildIntermediatePath(self, target):
-    return os.path.join(
-        self._root_path, self._TARGET_INTERMEDIATES_BASE_PATH, target.GetType(),
-        "%s_intermediates" % target.GetName())
-
-  def _GatherSrcs(self, targets):
-    """Builds the emma input source path arguments from provided targets.
-
-    Args:
-      targets: list of CoverageTargets
-    Returns:
-      source path arguments string
-    """
-    src_list = []
-    for target in targets:
-      target_srcs = target.GetPaths()
-      for path in target_srcs:
-        src_list.append("-sp %s" %  os.path.join(self._root_path, path))
-    return " ".join(src_list)
-
-  def _MergeFiles(self, input_paths, dest_path):
-    """Merges a set of emma coverage files into a consolidated file.
-
-    Args:
-      input_paths: list of string absolute coverage file paths to merge
-      dest_path: absolute file path of destination file
-    """
-    input_list = []
-    for input_path in input_paths:
-      input_list.append("-in %s" % input_path)
-    input_args = " ".join(input_list)
-    self._RunCmd("java -cp %s emma merge %s -out %s" % (self._emma_jar_path,
-                                                        input_args, dest_path))
-
-  def _RunCmd(self, cmd):
-    """Runs and logs the given os command."""
-    run_command.RunCommand(cmd, return_output=False)
-
-  def _CombineTargetCoverage(self):
-    """Combines all target mode code coverage results.
-
-    Will find all code coverage data files in direct sub-directories of
-    self._output_root_path, and combine them into a single coverage report.
-    Generated report is placed at self._output_root_path/android.html
-    """
-    coverage_files = self._FindCoverageFiles(self._output_root_path)
-    combined_coverage = os.path.join(self._output_root_path,
-                                     "android.%s" % self._TEST_COVERAGE_EXT)
-    self._MergeFiles(coverage_files, combined_coverage)
-    report_path = os.path.join(self._output_root_path, "android")
-    # don't link to source, to limit file size
-    self._GenerateReport(report_path, combined_coverage,
-                         self._targets_manifest.GetTargets(), do_src=False)
-
-  def _CombineTestCoverage(self):
-    """Consolidates code coverage results for all target result directories."""
-    target_dirs = os.listdir(self._output_root_path)
-    for target_name in target_dirs:
-      output_path = os.path.join(self._output_root_path, target_name)
-      target = self._targets_manifest.GetTarget(target_name)
-      if os.path.isdir(output_path) and target is not None:
-        coverage_files = self._FindCoverageFiles(output_path)
-        combined_coverage = os.path.join(output_path, "%s.%s" %
-                                         (target_name, self._TEST_COVERAGE_EXT))
-        self._MergeFiles(coverage_files, combined_coverage)
-        report_path = os.path.join(output_path, target_name)
-        self._GenerateReport(report_path, combined_coverage, [target])
-      else:
-        logger.Log("%s is not a valid target directory, skipping" % output_path)
-
-  def _FindCoverageFiles(self, root_path):
-    """Finds all files in <root_path>/*/*.<_TEST_COVERAGE_EXT>.
-
-    Args:
-      root_path: absolute file path string to search from
-    Returns:
-      list of absolute file path strings of coverage files
-    """
-    file_pattern = os.path.join(root_path, "*", "*.%s" %
-                                self._TEST_COVERAGE_EXT)
-    coverage_files = glob.glob(file_pattern)
-    return coverage_files
-
-  def _ReadTargets(self):
-    """Parses the set of coverage target data.
-
-    Returns:
-       a CoverageTargets object that contains set of parsed targets.
-    Raises:
-       AbortError if a fatal error occurred when parsing the target files.
-    """
-    core_target_path = os.path.join(self._root_path, self._CORE_TARGET_PATH)
-    try:
-      targets = coverage_targets.CoverageTargets()
-      targets.Parse(core_target_path)
-      vendor_targets_pattern = os.path.join(self._root_path,
-                                            self._VENDOR_TARGET_PATH)
-      target_file_paths = glob.glob(vendor_targets_pattern)
-      for target_file_path in target_file_paths:
-        targets.Parse(target_file_path)
-      return targets
-    except errors.ParseError:
-      raise errors.AbortError
-
-  def TidyOutput(self):
-    """Runs tidy on all generated html files.
-
-    This is needed to the html files can be displayed cleanly on a web server.
-    Assumes tidy is on current PATH.
-    """
-    logger.Log("Tidying output files")
-    self._TidyDir(self._output_root_path)
-
-  def _TidyDir(self, dir_path):
-    """Recursively tidy all html files in given dir_path."""
-    html_file_pattern = os.path.join(dir_path, "*.html")
-    html_files_iter = glob.glob(html_file_pattern)
-    for html_file_path in html_files_iter:
-      os.system("tidy -m -errors -quiet %s" % html_file_path)
-    sub_dirs = os.listdir(dir_path)
-    for sub_dir_name in sub_dirs:
-      sub_dir_path = os.path.join(dir_path, sub_dir_name)
-      if os.path.isdir(sub_dir_path):
-        self._TidyDir(sub_dir_path)
-
-  def CombineCoverage(self):
-    """Create combined coverage reports for all targets and tests."""
-    self._CombineTestCoverage()
-    self._CombineTargetCoverage()
-
-
-def EnableCoverageBuild():
-  """Enable building an Android target with code coverage instrumentation."""
-  os.environ["EMMA_INSTRUMENT"] = "true"
-
-
-def TestDeviceCoverageSupport(adb):
-  """Check if device has support for generating code coverage metrics.
-
-  This tries to dump emma help information on device, a response containing
-  help information will indicate that emma is already on system class path.
-
-  Returns:
-    True if device can support code coverage. False otherwise.
-  """
-  try:
-    output = adb.SendShellCommand("exec app_process / emma -h")
-
-    if output.find('emma usage:') == 0:
-      return True
-  except errors.AbortError:
-    pass
-  return False
-
-
-def Run():
-  """Does coverage operations based on command line args."""
-  # TODO: do we want to support combining coverage for a single target
-
-  try:
-    parser = optparse.OptionParser(usage="usage: %prog --combine-coverage")
-    parser.add_option(
-        "-c", "--combine-coverage", dest="combine_coverage", default=False,
-        action="store_true", help="Combine coverage results stored given "
-        "android root path")
-    parser.add_option(
-        "-t", "--tidy", dest="tidy", default=False, action="store_true",
-        help="Run tidy on all generated html files")
-
-    options, args = parser.parse_args()
-
-    coverage = CoverageGenerator(None)
-    if options.combine_coverage:
-      coverage.CombineCoverage()
-    if options.tidy:
-      coverage.TidyOutput()
-  except errors.AbortError:
-    logger.SilentLog("Exiting due to AbortError")
-
-if __name__ == "__main__":
-  Run()
diff --git a/testrunner/coverage/__init__.py b/testrunner/coverage/__init__.py
new file mode 100644
index 0000000..4be2078
--- /dev/null
+++ b/testrunner/coverage/__init__.py
@@ -0,0 +1 @@
+__all__ = ['coverage', 'coverage_targets', 'coverage_target']
diff --git a/testrunner/coverage/coverage.py b/testrunner/coverage/coverage.py
new file mode 100755
index 0000000..570527d
--- /dev/null
+++ b/testrunner/coverage/coverage.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utilities for generating code coverage reports for Android tests."""
+
+# Python imports
+import glob
+import optparse
+import os
+
+# local imports
+import android_build
+import android_mk
+import coverage_target
+import coverage_targets
+import errors
+import logger
+import run_command
+
+
+class CoverageGenerator(object):
+  """Helper utility for obtaining code coverage results on Android.
+
+  Intended to simplify the process of building,running, and generating code
+  coverage results for a pre-defined set of tests and targets
+  """
+
+    # path to EMMA host jar, relative to Android build root
+  _EMMA_JAR = os.path.join("external", "emma", "lib", "emma.jar")
+  _TEST_COVERAGE_EXT = "ec"
+  # root path of generated coverage report files, relative to Android build root
+  _COVERAGE_REPORT_PATH = os.path.join("out", "emma")
+  _TARGET_DEF_FILE = "coverage_targets.xml"
+  _CORE_TARGET_PATH = os.path.join("development", "testrunner",
+                                   _TARGET_DEF_FILE)
+  # vendor glob file path patterns to tests, relative to android
+  # build root
+  _VENDOR_TARGET_PATH = os.path.join("vendor", "*", "tests", "testinfo",
+                                     _TARGET_DEF_FILE)
+
+  # path to root of target build intermediates
+  _TARGET_INTERMEDIATES_BASE_PATH = os.path.join("out", "target", "common",
+                                                 "obj")
+
+  def __init__(self, adb_interface):
+    self._root_path = android_build.GetTop()
+    self._output_root_path = os.path.join(self._root_path,
+                                          self._COVERAGE_REPORT_PATH)
+    self._emma_jar_path = os.path.join(self._root_path, self._EMMA_JAR)
+    self._adb = adb_interface
+    self._targets_manifest = self._ReadTargets()
+
+  def ExtractReport(self,
+                    test_suite_name,
+                    target,
+                    device_coverage_path,
+                    output_path=None,
+                    test_qualifier=None):
+    """Extract runtime coverage data and generate code coverage report.
+
+    Assumes test has just been executed.
+    Args:
+      test_suite_name: name of TestSuite to generate coverage data for
+      target: the CoverageTarget to use as basis for coverage calculation
+      device_coverage_path: location of coverage file on device
+      output_path: path to place output files in. If None will use
+        <android_root_path>/<_COVERAGE_REPORT_PATH>/<target>/<test[-qualifier]>
+      test_qualifier: designates mode test was run with. e.g size=small.
+        If not None, this will be used to customize output_path as shown above.
+
+    Returns:
+      absolute file path string of generated html report file.
+    """
+    if output_path is None:
+      report_name = test_suite_name
+      if test_qualifier:
+        report_name = report_name + "-" + test_qualifier
+      output_path = os.path.join(self._root_path,
+                                 self._COVERAGE_REPORT_PATH,
+                                 target.GetName(),
+                                 report_name)
+
+    coverage_local_name = "%s.%s" % (report_name,
+                                     self._TEST_COVERAGE_EXT)
+    coverage_local_path = os.path.join(output_path,
+                                       coverage_local_name)
+    if self._adb.Pull(device_coverage_path, coverage_local_path):
+
+      report_path = os.path.join(output_path,
+                                 report_name)
+      return self._GenerateReport(report_path, coverage_local_path, [target],
+                                  do_src=True)
+    return None
+
+  def _GenerateReport(self, report_path, coverage_file_path, targets,
+                      do_src=True):
+    """Generate the code coverage report.
+
+    Args:
+      report_path: absolute file path of output file, without extension
+      coverage_file_path: absolute file path of code coverage result file
+      targets: list of CoverageTargets to use as base for code coverage
+          measurement.
+      do_src: True if generate coverage report with source linked in.
+          Note this will increase size of generated report.
+
+    Returns:
+      absolute file path to generated report file.
+    """
+    input_metadatas = self._GatherMetadatas(targets)
+
+    if do_src:
+      src_arg = self._GatherSrcs(targets)
+    else:
+      src_arg = ""
+
+    report_file = "%s.html" % report_path
+    cmd1 = ("java -cp %s emma report -r html -in %s %s %s " %
+            (self._emma_jar_path, coverage_file_path, input_metadatas, src_arg))
+    cmd2 = "-Dreport.html.out.file=%s" % report_file
+    self._RunCmd(cmd1 + cmd2)
+    return report_file
+
+  def _GatherMetadatas(self, targets):
+    """Builds the emma input metadata argument from provided targets.
+
+    Args:
+      targets: list of CoverageTargets
+
+    Returns:
+      input metadata argument string
+    """
+    input_metadatas = ""
+    for target in targets:
+      input_metadata = os.path.join(self._GetBuildIntermediatePath(target),
+                                    "coverage.em")
+      input_metadatas += " -in %s" % input_metadata
+    return input_metadatas
+
+  def _GetBuildIntermediatePath(self, target):
+    return os.path.join(
+        self._root_path, self._TARGET_INTERMEDIATES_BASE_PATH, target.GetType(),
+        "%s_intermediates" % target.GetName())
+
+  def _GatherSrcs(self, targets):
+    """Builds the emma input source path arguments from provided targets.
+
+    Args:
+      targets: list of CoverageTargets
+    Returns:
+      source path arguments string
+    """
+    src_list = []
+    for target in targets:
+      target_srcs = target.GetPaths()
+      for path in target_srcs:
+        src_list.append("-sp %s" %  os.path.join(self._root_path, path))
+    return " ".join(src_list)
+
+  def _MergeFiles(self, input_paths, dest_path):
+    """Merges a set of emma coverage files into a consolidated file.
+
+    Args:
+      input_paths: list of string absolute coverage file paths to merge
+      dest_path: absolute file path of destination file
+    """
+    input_list = []
+    for input_path in input_paths:
+      input_list.append("-in %s" % input_path)
+    input_args = " ".join(input_list)
+    self._RunCmd("java -cp %s emma merge %s -out %s" % (self._emma_jar_path,
+                                                        input_args, dest_path))
+
+  def _RunCmd(self, cmd):
+    """Runs and logs the given os command."""
+    run_command.RunCommand(cmd, return_output=False)
+
+  def _CombineTargetCoverage(self):
+    """Combines all target mode code coverage results.
+
+    Will find all code coverage data files in direct sub-directories of
+    self._output_root_path, and combine them into a single coverage report.
+    Generated report is placed at self._output_root_path/android.html
+    """
+    coverage_files = self._FindCoverageFiles(self._output_root_path)
+    combined_coverage = os.path.join(self._output_root_path,
+                                     "android.%s" % self._TEST_COVERAGE_EXT)
+    self._MergeFiles(coverage_files, combined_coverage)
+    report_path = os.path.join(self._output_root_path, "android")
+    # don't link to source, to limit file size
+    self._GenerateReport(report_path, combined_coverage,
+                         self._targets_manifest.GetTargets(), do_src=False)
+
+  def _CombineTestCoverage(self):
+    """Consolidates code coverage results for all target result directories."""
+    target_dirs = os.listdir(self._output_root_path)
+    for target_name in target_dirs:
+      output_path = os.path.join(self._output_root_path, target_name)
+      target = self._targets_manifest.GetTarget(target_name)
+      if os.path.isdir(output_path) and target is not None:
+        coverage_files = self._FindCoverageFiles(output_path)
+        combined_coverage = os.path.join(output_path, "%s.%s" %
+                                         (target_name, self._TEST_COVERAGE_EXT))
+        self._MergeFiles(coverage_files, combined_coverage)
+        report_path = os.path.join(output_path, target_name)
+        self._GenerateReport(report_path, combined_coverage, [target])
+      else:
+        logger.Log("%s is not a valid target directory, skipping" % output_path)
+
+  def _FindCoverageFiles(self, root_path):
+    """Finds all files in <root_path>/*/*.<_TEST_COVERAGE_EXT>.
+
+    Args:
+      root_path: absolute file path string to search from
+    Returns:
+      list of absolute file path strings of coverage files
+    """
+    file_pattern = os.path.join(root_path, "*", "*.%s" %
+                                self._TEST_COVERAGE_EXT)
+    coverage_files = glob.glob(file_pattern)
+    return coverage_files
+
+  def _ReadTargets(self):
+    """Parses the set of coverage target data.
+
+    Returns:
+       a CoverageTargets object that contains set of parsed targets.
+    Raises:
+       AbortError if a fatal error occurred when parsing the target files.
+    """
+    core_target_path = os.path.join(self._root_path, self._CORE_TARGET_PATH)
+    try:
+      targets = coverage_targets.CoverageTargets()
+      targets.Parse(core_target_path)
+      vendor_targets_pattern = os.path.join(self._root_path,
+                                            self._VENDOR_TARGET_PATH)
+      target_file_paths = glob.glob(vendor_targets_pattern)
+      for target_file_path in target_file_paths:
+        targets.Parse(target_file_path)
+      return targets
+    except errors.ParseError:
+      raise errors.AbortError
+
+  def TidyOutput(self):
+    """Runs tidy on all generated html files.
+
+    This is needed to the html files can be displayed cleanly on a web server.
+    Assumes tidy is on current PATH.
+    """
+    logger.Log("Tidying output files")
+    self._TidyDir(self._output_root_path)
+
+  def _TidyDir(self, dir_path):
+    """Recursively tidy all html files in given dir_path."""
+    html_file_pattern = os.path.join(dir_path, "*.html")
+    html_files_iter = glob.glob(html_file_pattern)
+    for html_file_path in html_files_iter:
+      os.system("tidy -m -errors -quiet %s" % html_file_path)
+    sub_dirs = os.listdir(dir_path)
+    for sub_dir_name in sub_dirs:
+      sub_dir_path = os.path.join(dir_path, sub_dir_name)
+      if os.path.isdir(sub_dir_path):
+        self._TidyDir(sub_dir_path)
+
+  def CombineCoverage(self):
+    """Create combined coverage reports for all targets and tests."""
+    self._CombineTestCoverage()
+    self._CombineTargetCoverage()
+
+  def GetCoverageTarget(self, name):
+    """Find the CoverageTarget for given name"""
+    target = self._targets_manifest.GetTarget(name)
+    if target is None:
+      msg = ["Error: test references undefined target %s." % name]
+      msg.append(" Ensure target is defined in %s" % self._TARGET_DEF_FILE)
+      raise errors.AbortError(msg)
+    return target
+
+  def GetCoverageTargetForPath(self, path):
+    """Find the CoverageTarget for given file system path"""
+    android_mk_path = os.path.join(path, "Android.mk")
+    if os.path.exists(android_mk_path):
+      android_mk_parser = android_mk.CreateAndroidMK(path)
+      target = coverage_target.CoverageTarget()
+      target.SetBuildPath(os.path.join(path, "src"))
+      target.SetName(android_mk_parser.GetVariable(android_mk_parser.PACKAGE_NAME))
+      target.SetType("APPS")
+      return target
+    else:
+      msg = "No Android.mk found at %s" % path
+      raise errors.AbortError(msg)
+
+
+def EnableCoverageBuild():
+  """Enable building an Android target with code coverage instrumentation."""
+  os.environ["EMMA_INSTRUMENT"] = "true"
+
+def Run():
+  """Does coverage operations based on command line args."""
+  # TODO: do we want to support combining coverage for a single target
+
+  try:
+    parser = optparse.OptionParser(usage="usage: %prog --combine-coverage")
+    parser.add_option(
+        "-c", "--combine-coverage", dest="combine_coverage", default=False,
+        action="store_true", help="Combine coverage results stored given "
+        "android root path")
+    parser.add_option(
+        "-t", "--tidy", dest="tidy", default=False, action="store_true",
+        help="Run tidy on all generated html files")
+
+    options, args = parser.parse_args()
+
+    coverage = CoverageGenerator(None)
+    if options.combine_coverage:
+      coverage.CombineCoverage()
+    if options.tidy:
+      coverage.TidyOutput()
+  except errors.AbortError:
+    logger.SilentLog("Exiting due to AbortError")
+
+if __name__ == "__main__":
+  Run()
diff --git a/testrunner/coverage/coverage_target.py b/testrunner/coverage/coverage_target.py
new file mode 100644
index 0000000..9d08a5c
--- /dev/null
+++ b/testrunner/coverage/coverage_target.py
@@ -0,0 +1,48 @@
+#
+# 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.
+
+class CoverageTarget:
+  """ Represents a code coverage target definition"""
+
+  def __init__(self):
+    self._name = None
+    self._type = None
+    self._build_path = None
+    self._paths = []
+
+  def GetName(self):
+    return self._name
+
+  def SetName(self, name):
+    self._name = name
+
+  def GetPaths(self):
+    return self._paths
+
+  def AddPath(self, path):
+    self._paths.append(path)
+
+  def GetType(self):
+    return self._type
+
+  def SetType(self, buildtype):
+    self._type = buildtype
+
+  def GetBuildPath(self):
+    return self._build_path
+
+  def SetBuildPath(self, build_path):
+    self._build_path = build_path
+
diff --git a/testrunner/coverage/coverage_targets.py b/testrunner/coverage/coverage_targets.py
new file mode 100644
index 0000000..1198805
--- /dev/null
+++ b/testrunner/coverage/coverage_targets.py
@@ -0,0 +1,128 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import xml.dom.minidom
+import xml.parsers
+import os
+
+
+import coverage_target
+import logger
+import errors
+
+class CoverageTargets:
+  """Accessor for the code coverage target xml file
+  Expects the following format:
+  <targets>
+    <target
+      name=""
+      type="JAVA_LIBRARIES|APPS"
+      build_path=""
+
+      [<src path=""/>] (0..*)  - These are relative to build_path. If missing,
+                                 assumes 'src'
+    >/target>
+
+    TODO: add more format checking
+  """
+
+  _TARGET_TAG_NAME = 'coverage_target'
+  _NAME_ATTR = 'name'
+  _TYPE_ATTR = 'type'
+  _BUILD_ATTR = 'build_path'
+  _SRC_TAG = 'src'
+  _PATH_ATTR = 'path'
+
+  def __init__(self, ):
+    self._target_map= {}
+
+  def __iter__(self):
+    return iter(self._target_map.values())
+
+  def Parse(self, file_path):
+    """Parse the coverage target data from from given file path, and add it to
+       the current object
+       Args:
+         file_path: absolute file path to parse
+       Raises:
+         errors.ParseError if file_path cannot be parsed
+    """
+    try:
+      doc = xml.dom.minidom.parse(file_path)
+    except IOError:
+      # Error: The results file does not exist
+      logger.Log('Results file %s does not exist' % file_path)
+      raise errors.ParseError
+    except xml.parsers.expat.ExpatError:
+      logger.Log('Error Parsing xml file: %s ' %  file_path)
+      raise errors.ParseError
+
+    target_elements = doc.getElementsByTagName(self._TARGET_TAG_NAME)
+
+    for target_element in target_elements:
+      target = coverage_target.CoverageTarget()
+      self._ParseCoverageTarget(target, target_element)
+      self._AddTarget(target)
+
+  def _AddTarget(self, target):
+    self._target_map[target.GetName()] = target
+
+  def GetBuildTargets(self):
+    """ returns list of target names """
+    build_targets = []
+    for target in self:
+      build_targets.append(target.GetName())
+    return build_targets
+
+  def GetTargets(self):
+    """ returns list of CoverageTarget"""
+    return self._target_map.values()
+
+  def GetTarget(self, name):
+    """ returns CoverageTarget for given name. None if not found """
+    try:
+      return self._target_map[name]
+    except KeyError:
+      return None
+
+  def _ParseCoverageTarget(self, target, target_element):
+    """Parse coverage data from XML.
+
+    Args:
+      target: the Coverage object to populate
+      target_element: the XML element to get data from
+    """
+    target.SetName(target_element.getAttribute(self._NAME_ATTR))
+    target.SetType(target_element.getAttribute(self._TYPE_ATTR))
+    target.SetBuildPath(target_element.getAttribute(self._BUILD_ATTR))
+    self._paths = []
+    self._ParsePaths(target, target_element)
+
+  def _ParsePaths(self, target, target_element):
+    src_elements = target_element.getElementsByTagName(self._SRC_TAG)
+    if len(src_elements) <= 0:
+      # no src tags specified. Assume build_path + src
+      target.AddPath(os.path.join(target.GetBuildPath(), "src"))
+    for src_element in src_elements:
+      rel_path = src_element.getAttribute(self._PATH_ATTR)
+      target.AddPath(os.path.join(target.GetBuildPath(), rel_path))
+
+
+def Parse(xml_file_path):
+  """parses out a file_path class from given path to xml"""
+  targets = CoverageTargets()
+  targets.Parse(xml_file_path)
+  return targets
diff --git a/testrunner/coverage_targets.py b/testrunner/coverage_targets.py
deleted file mode 100644
index bc826de..0000000
--- a/testrunner/coverage_targets.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/usr/bin/python2.4
-#
-#
-# Copyright 2008, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); 
-# you may not use this file except in compliance with the License. 
-# You may obtain a copy of the License at 
-#
-#     http://www.apache.org/licenses/LICENSE-2.0 
-#
-# Unless required by applicable law or agreed to in writing, software 
-# distributed under the License is distributed on an "AS IS" BASIS, 
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-# See the License for the specific language governing permissions and 
-# limitations under the License.
-import xml.dom.minidom
-import xml.parsers
-import os
-
-import logger
-import errors
-
-class CoverageTargets:
-  """Accessor for the code coverage target xml file 
-  Expects the following format:
-  <targets>
-    <target 
-      name=""
-      type="JAVA_LIBRARIES|APPS"
-      build_path=""
-      
-      [<src path=""/>] (0..*)  - These are relative to build_path. If missing, 
-                                 assumes 'src'
-    >/target>
-    
-    TODO: add more format checking  
-  """
-  
-  _TARGET_TAG_NAME = 'coverage_target' 
-  
-  def __init__(self, ):
-    self._target_map= {}
-    
-  def __iter__(self):
-    return iter(self._target_map.values())
-      
-  def Parse(self, file_path):
-    """Parse the coverage target data from from given file path, and add it to 
-       the current object
-       Args:
-         file_path: absolute file path to parse
-       Raises:
-         errors.ParseError if file_path cannot be parsed  
-    """
-    try:
-      doc = xml.dom.minidom.parse(file_path)
-    except IOError:
-      # Error: The results file does not exist 
-      logger.Log('Results file %s does not exist' % file_path)
-      raise errors.ParseError
-    except xml.parsers.expat.ExpatError:
-      logger.Log('Error Parsing xml file: %s ' %  file_path)
-      raise errors.ParseError
-    
-    target_elements = doc.getElementsByTagName(self._TARGET_TAG_NAME)
-
-    for target_element in target_elements:
-      target = CoverageTarget(target_element)
-      self._AddTarget(target)
-    
-  def _AddTarget(self, target): 
-    self._target_map[target.GetName()] = target
-     
-  def GetBuildTargets(self):
-    """ returns list of target names """
-    build_targets = []
-    for target in self:
-      build_targets.append(target.GetName())
-    return build_targets   
-  
-  def GetTargets(self):
-    """ returns list of CoverageTarget"""
-    return self._target_map.values()
-  
-  def GetTarget(self, name):
-    """ returns CoverageTarget for given name. None if not found """
-    try:
-      return self._target_map[name]
-    except KeyError:
-      return None
-  
-class CoverageTarget:
-  """ Represents one coverage target definition parsed from xml """
-  
-  _NAME_ATTR = 'name'
-  _TYPE_ATTR = 'type'
-  _BUILD_ATTR = 'build_path'
-  _SRC_TAG = 'src'
-  _PATH_ATTR = 'path'
-  
-  def __init__(self, target_element):
-    self._name = target_element.getAttribute(self._NAME_ATTR)
-    self._type = target_element.getAttribute(self._TYPE_ATTR)
-    self._build_path = target_element.getAttribute(self._BUILD_ATTR)
-    self._paths = []
-    self._ParsePaths(target_element)
-    
-  def GetName(self):
-    return self._name
-
-  def GetPaths(self):
-    return self._paths
-  
-  def GetType(self):
-    return self._type
-
-  def GetBuildPath(self):
-    return self._build_path
-  
-  def _ParsePaths(self, target_element):
-    src_elements = target_element.getElementsByTagName(self._SRC_TAG)
-    if len(src_elements) <= 0:
-      # no src tags specified. Assume build_path + src
-      self._paths.append(os.path.join(self.GetBuildPath(), "src"))
-    for src_element in src_elements:
-      rel_path = src_element.getAttribute(self._PATH_ATTR)
-      self._paths.append(os.path.join(self.GetBuildPath(), rel_path))
-  
-def Parse(xml_file_path):
-  """parses out a file_path class from given path to xml"""
-  targets = CoverageTargets()
-  targets.Parse(xml_file_path)
-  return targets
diff --git a/testrunner/make_tree.py b/testrunner/make_tree.py
new file mode 100644
index 0000000..c8bac17
--- /dev/null
+++ b/testrunner/make_tree.py
@@ -0,0 +1,116 @@
+#
+# 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.
+
+"""Data structure for processing makefiles."""
+
+import os
+
+import android_build
+import android_mk
+import errors
+
+class MakeNode(object):
+  """Represents single node in make tree."""
+
+  def __init__(self, name, parent):
+    self._name = name
+    self._children_map = {}
+    self._is_leaf = False
+    self._parent = parent
+    self._includes_submake = None
+    if parent:
+      self._path = os.path.join(parent._GetPath(), name)
+    else:
+      self._path = ""
+
+  def _AddPath(self, path_segs):
+    """Adds given path to this node.
+
+    Args:
+      path_segs: list of path segments
+    """
+    if not path_segs:
+      # done processing path
+      return self
+    current_seg = path_segs.pop(0)
+    child = self._children_map.get(current_seg)
+    if not child:
+      child = MakeNode(current_seg, self)
+      self._children_map[current_seg] = child
+    return child._AddPath(path_segs)
+
+  def _SetLeaf(self, is_leaf):
+    self._is_leaf = is_leaf
+
+  def _GetPath(self):
+    return self._path
+
+  def _DoesIncludesSubMake(self):
+    if self._includes_submake is None:
+      if self._is_leaf:
+        path = os.path.join(android_build.GetTop(), self._path)
+        mk_parser = android_mk.CreateAndroidMK(path)
+        self._includes_submake = mk_parser.IncludesMakefilesUnder()
+      else:
+        self._includes_submake = False
+    return self._includes_submake
+
+  def _DoesParentIncludeMe(self):
+    return self._parent and self._parent._DoesIncludesSubMake()
+
+  def _BuildPrunedMakeList(self, make_list):
+    if self._is_leaf and not self._DoesParentIncludeMe():
+      make_list.append(os.path.join(self._path, "Android.mk"))
+    for child in self._children_map.itervalues():
+      child._BuildPrunedMakeList(make_list)
+
+
+class MakeTree(MakeNode):
+  """Data structure for building a non-redundant set of Android.mk paths.
+
+  Used to collapse set of Android.mk files to use to prevent issuing make
+  command that include same module multiple times due to include rules.
+  """
+
+  def __init__(self):
+    super(MakeTree, self).__init__("", None)
+
+  def AddPath(self, path):
+    """Adds make directory path to tree.
+
+    Will have no effect if path is already included in make set.
+
+    Args:
+      path: filesystem path to directory to build, relative to build root.
+    """
+    path = os.path.normpath(path)
+    mk_path = os.path.join(android_build.GetTop(), path, "Android.mk")
+    if not os.path.isfile(mk_path):
+      raise errors.AbortError("%s does not exist" % mk_path)
+    path_segs = path.split(os.sep)
+    child = self._AddPath(path_segs)
+    child._SetLeaf(True)
+
+  def GetPrunedMakeList(self):
+    """Return as list of the minimum set of Android.mk files necessary to
+    build all leaf nodes in tree.
+    """
+    make_list = []
+    self._BuildPrunedMakeList(make_list)
+    return make_list
+
+  def IsEmpty(self):
+    return not self._children_map
+
diff --git a/testrunner/runtest.py b/testrunner/runtest.py
index 6c1b1b6..f7a4759 100755
--- a/testrunner/runtest.py
+++ b/testrunner/runtest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2.4
+#!/usr/bin/env python
 #
 # Copyright 2008, The Android Open Source Project
 #
@@ -33,6 +33,7 @@
 import glob
 import optparse
 import os
+import re
 from sets import Set
 import sys
 import time
@@ -40,9 +41,10 @@
 # local imports
 import adb_interface
 import android_build
-import coverage
+from coverage import coverage
 import errors
 import logger
+import make_tree
 import run_command
 from test_defs import test_defs
 from test_defs import test_walker
@@ -73,6 +75,13 @@
 
   _DALVIK_VERIFIER_OFF_PROP = "dalvik.vm.dexopt-flags = v=n"
 
+  # regular expression to match install: statements in make output
+  _RE_MAKE_INSTALL = re.compile(r'Install:\s(.+)')
+
+  # regular expression to find remote device path from a file path relative
+  # to build root
+  _RE_MAKE_INSTALL_PATH = re.compile(r'out\/target\/product\/\w+\/(.+)$')
+
   def __init__(self):
     # disable logging of timestamp
     self._root_path = android_build.GetTop()
@@ -135,6 +144,9 @@
     parser.add_option("-o", "--coverage", dest="coverage",
                       default=False, action="store_true",
                       help="Generate code coverage metrics for test(s)")
+    parser.add_option("--coverage-target", dest="coverage_target_path",
+                      default=None,
+                      help="Path to app to collect code coverage target data for.")
     parser.add_option("-x", "--path", dest="test_path",
                       help="Run test(s) at given file system path")
     parser.add_option("-t", "--all-tests", dest="all_tests",
@@ -183,6 +195,9 @@
     if self._options.verbose:
       logger.SetVerbose(True)
 
+    if self._options.coverage_target_path:
+      self._options.coverage = True
+
     self._known_tests = self._ReadTests()
 
     self._options.host_lib_path = android_build.GetHostLibraryPath()
@@ -233,35 +248,24 @@
     self._TurnOffVerifier(tests)
     self._DoFullBuild(tests)
 
-    target_set = []
+    target_tree = make_tree.MakeTree()
 
     extra_args_set = []
     for test_suite in tests:
-      self._AddBuildTarget(test_suite, target_set, extra_args_set)
+      self._AddBuildTarget(test_suite, target_tree, extra_args_set)
 
     if not self._options.preview:
       self._adb.EnableAdbRoot()
     else:
       logger.Log("adb root")
-    rebuild_libcore = False
-    if target_set:
+
+    if not target_tree.IsEmpty():
       if self._options.coverage:
         coverage.EnableCoverageBuild()
-        # hack to remove core library intermediates
-        # hack is needed because:
-        # 1. EMMA_INSTRUMENT changes what source files to include in libcore
-        #    but it does not trigger a rebuild
-        # 2. there's no target (like "clear-intermediates") to remove the files
-        #    decently
-        rebuild_libcore = not coverage.TestDeviceCoverageSupport(self._adb)
-        if rebuild_libcore:
-          cmd = "rm -rf %s" % os.path.join(
-              self._root_path,
-              "out/target/common/obj/JAVA_LIBRARIES/core_intermediates/")
-          logger.Log(cmd)
-          run_command.RunCommand(cmd, return_output=False)
+        target_tree.AddPath("external/emma")
 
-      target_build_string = " ".join(target_set)
+      target_list = target_tree.GetPrunedMakeList()
+      target_build_string = " ".join(target_list)
       extra_args_string = " ".join(extra_args_set)
 
       # mmm cannot be used from python, so perform a similar operation using
@@ -270,17 +274,38 @@
           target_build_string, self._options.make_jobs, self._root_path,
           extra_args_string)
       logger.Log(cmd)
+      if not self._options.preview:
+        output = run_command.RunCommand(cmd, return_output=True, timeout_time=600)
+        self._DoInstall(output)
 
-      if self._options.preview:
-        # in preview mode, just display to the user what command would have been
-        # run
-        logger.Log("adb sync")
-      else:
-        # set timeout for build to 10 minutes, since libcore may need to
-        # be rebuilt
-        run_command.RunCommand(cmd, return_output=False, timeout_time=600)
-        logger.Log("Syncing to device...")
-        self._adb.Sync(runtime_restart=rebuild_libcore)
+  def _DoInstall(self, make_output):
+    """Install artifacts from build onto device.
+
+    Looks for 'install:' text from make output to find artifacts to install.
+
+    Args:
+      make_output: stdout from make command
+    """
+    for line in make_output.split("\n"):
+      m = self._RE_MAKE_INSTALL.match(line)
+      if m:
+        install_path = m.group(1)
+        if install_path.endswith(".apk"):
+          abs_install_path = os.path.join(self._root_path, install_path)
+          logger.Log("adb install -r %s" % abs_install_path)
+          logger.Log(self._adb.Install(abs_install_path))
+        else:
+          self._PushInstallFileToDevice(install_path)
+
+  def _PushInstallFileToDevice(self, install_path):
+    m = self._RE_MAKE_INSTALL_PATH.match(install_path)
+    if m:
+      remote_path = m.group(1)
+      abs_install_path = os.path.join(self._root_path, install_path)
+      logger.Log("adb push %s %s" % (abs_install_path, remote_path))
+      self._adb.Push(abs_install_path, remote_path)
+    else:
+      logger.Log("Error: Failed to recognize path of file to install %s" % install_path)
 
   def _DoFullBuild(self, tests):
     """If necessary, run a full 'make' command for the tests that need it."""
@@ -309,25 +334,22 @@
       if not self._options.preview:
         old_dir = os.getcwd()
         os.chdir(self._root_path)
-        run_command.RunCommand(cmd, return_output=False)
+        output = run_command.RunCommand(cmd, return_output=True)
         os.chdir(old_dir)
+        self._DoInstall(output)
 
-  def _AddBuildTarget(self, test_suite, target_set, extra_args_set):
+  def _AddBuildTarget(self, test_suite, target_tree, extra_args_set):
     if not test_suite.IsFullMake():
       build_dir = test_suite.GetBuildPath()
-      if self._AddBuildTargetPath(build_dir, target_set):
+      if self._AddBuildTargetPath(build_dir, target_tree):
         extra_args_set.append(test_suite.GetExtraBuildArgs())
       for path in test_suite.GetBuildDependencies(self._options):
-        self._AddBuildTargetPath(path, target_set)
+        self._AddBuildTargetPath(path, target_tree)
 
-  def _AddBuildTargetPath(self, build_dir, target_set):
+  def _AddBuildTargetPath(self, build_dir, target_tree):
     if build_dir is not None:
-      build_file_path = os.path.join(build_dir, "Android.mk")
-      if os.path.isfile(os.path.join(self._root_path, build_file_path)):
-        target_set.append(build_file_path)
-        return True
-      else:
-        logger.Log("%s has no Android.mk, skipping" % build_dir)
+      target_tree.AddPath(build_dir)
+      return True
     return False
 
   def _GetTestsToRun(self):
@@ -369,7 +391,7 @@
     If one or more tests needs dalvik verifier off, and it is not already off,
     turns off verifier and reboots device to allow change to take effect.
     """
-    # hack to check if these are framework/base tests. If so, turn off verifier
+    # hack to check if these are frameworks/base tests. If so, turn off verifier
     # to allow framework tests to access package-private framework api
     framework_test = False
     for test in test_list:
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 801e35d..8494cb0 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -295,7 +295,7 @@
 <test name="cts-hardware"
     build_path="cts/tests/tests/hardware"
     package="com.android.cts.hardware"
-    runner="android.test.InstrumentationTestRunner"
+    runner="android.test.InstrumentationCtsTestRunner"
     coverage_target="framework"
     continuous="true"
     suite="cts" />
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
index ede7ceb..092a773 100644
--- a/testrunner/test_defs/instrumentation_test.py
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -22,7 +22,7 @@
 
 # local imports
 import android_manifest
-import coverage
+from coverage import coverage
 import errors
 import logger
 import test_suite
@@ -33,9 +33,6 @@
 
   DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
 
-  # dependency on libcore (used for Emma)
-  _LIBCORE_BUILD_PATH = "libcore"
-
   def __init__(self):
     test_suite.AbstractTestSuite.__init__(self)
     self._package_name = None
@@ -87,8 +84,8 @@
     return self
 
   def GetBuildDependencies(self, options):
-    if options.coverage:
-      return [self._LIBCORE_BUILD_PATH]
+    if options.coverage_target_path:
+      return [options.coverage_target_path]
     return []
 
   def Run(self, options, adb):
@@ -145,8 +142,11 @@
       logger.Log(adb_cmd)
     elif options.coverage:
       coverage_gen = coverage.CoverageGenerator(adb)
-      adb.WaitForInstrumentation(self.GetPackageName(),
-                                 self.GetRunnerName())
+      if options.coverage_target_path:
+        coverage_target = coverage_gen.GetCoverageTargetForPath(options.coverage_target_path)
+      elif self.GetTargetName():
+        coverage_target = coverage_gen.GetCoverageTarget(self.GetTargetName())
+      self._CheckInstrumentationInstalled(adb)
       # need to parse test output to determine path to coverage file
       logger.Log("Running in coverage mode, suppressing test output")
       try:
@@ -164,17 +164,26 @@
         return
 
       coverage_file = coverage_gen.ExtractReport(
-          self, device_coverage_path, test_qualifier=options.test_size)
+          self.GetName(), coverage_target, device_coverage_path,
+          test_qualifier=options.test_size)
       if coverage_file is not None:
         logger.Log("Coverage report generated at %s" % coverage_file)
+
     else:
-      adb.WaitForInstrumentation(self.GetPackageName(),
-                                 self.GetRunnerName())
-      adb.StartInstrumentationNoResults(
-          package_name=self.GetPackageName(),
-          runner_name=self.GetRunnerName(),
-          raw_mode=options.raw_mode,
-          instrumentation_args=instrumentation_args)
+      self._CheckInstrumentationInstalled(adb)
+      adb.StartInstrumentationNoResults(package_name=self.GetPackageName(),
+                                        runner_name=self.GetRunnerName(),
+                                        raw_mode=options.raw_mode,
+                                        instrumentation_args=
+                                        instrumentation_args)
+
+  def _CheckInstrumentationInstalled(self, adb):
+    if not adb.IsInstrumentationInstalled(self.GetPackageName(),
+                                          self.GetRunnerName()):
+      msg=("Could not find instrumentation %s/%s on device. Try forcing a "
+           "rebuild by updating a source file, and re-executing runtest." %
+           (self.GetPackageName(), self.GetRunnerName()))
+      raise errors.AbortError(msg=msg)
 
   def _PrintTestResults(self, test_results):
     """Prints a summary of test result data to stdout.
@@ -198,7 +207,6 @@
     logger.Log("Tests run: %d, Failures: %d, Errors: %d" %
                (total_count, fail_count, error_count))
 
-
 def HasInstrumentationTest(path):
   """Determine if given path defines an instrumentation test.
 
diff --git a/testrunner/test_defs/test_walker.py b/testrunner/test_defs/test_walker.py
index 572f8b6..fa6ea1b 100755
--- a/testrunner/test_defs/test_walker.py
+++ b/testrunner/test_defs/test_walker.py
@@ -141,6 +141,9 @@
       else:
         tests.extend(self._CreateSuites(android_mk_parser, path,
                                         upstream_build_path))
+      # TODO: remove this logic, and rely on caller to collapse build
+      # paths via make_tree
+
       # Try to build as much of original path as possible, so
       # keep track of upper-most parent directory where Android.mk was found
       # that has rule to build sub-directory makefiles.
@@ -148,7 +151,7 @@
       # ie if a test exists at 'foo' directory  and 'foo/sub', attempting to
       # build both 'foo' and 'foo/sub' will fail.
 
-      if android_mk_parser.HasInclude('call all-makefiles-under,$(LOCAL_PATH)'):
+      if android_mk_parser.IncludesMakefilesUnder():
         # found rule to build sub-directories. The parent path can be used,
         # or if not set, use current path
         if not upstream_build_path:
diff --git a/tools/emulator/opengl/system/OpenglSystemCommon/gralloc_cb.h b/tools/emulator/opengl/system/OpenglSystemCommon/gralloc_cb.h
index e879409..a207401 100644
--- a/tools/emulator/opengl/system/OpenglSystemCommon/gralloc_cb.h
+++ b/tools/emulator/opengl/system/OpenglSystemCommon/gralloc_cb.h
@@ -30,17 +30,18 @@
 struct cb_handle_t : public native_handle {
 
     cb_handle_t(int p_fd, int p_ashmemSize, int p_usage,
-                int p_width, int p_height,
+                int p_width, int p_height, int p_format,
                 int p_glFormat, int p_glType) :
         fd(p_fd),
         magic(BUFFER_HANDLE_MAGIC),
         usage(p_usage),
         width(p_width),
         height(p_height),
+        format(p_format),
         glFormat(p_glFormat),
         glType(p_glType),
         ashmemSize(p_ashmemSize),
-        ashmemBase(NULL),
+        ashmemBase(0),
         ashmemBasePid(0),
         mappedPid(0),
         lockedLeft(0),
@@ -88,6 +89,7 @@
     int usage;              // usage bits the buffer was created with
     int width;              // buffer width
     int height;             // buffer height
+    int format;             // real internal pixel format format
     int glFormat;           // OpenGL format enum used for host h/w color buffer
     int glType;             // OpenGL type enum used when uploading to host
     int ashmemSize;         // ashmem region size for the buffer (0 unless is HW_FB buffer or
diff --git a/tools/emulator/opengl/system/egl/egl.cpp b/tools/emulator/opengl/system/egl/egl.cpp
index ee195ac..da89c4d 100644
--- a/tools/emulator/opengl/system/egl/egl.cpp
+++ b/tools/emulator/opengl/system/egl/egl.cpp
@@ -263,10 +263,9 @@
 
 EGLBoolean egl_window_surface_t::init()
 {
-    if (nativeWindow->dequeueBuffer(nativeWindow, &buffer) != NO_ERROR) {
+    if (nativeWindow->dequeueBuffer_DEPRECATED(nativeWindow, &buffer) != NO_ERROR) {
         setErrorReturn(EGL_BAD_ALLOC, EGL_FALSE);
     }
-    nativeWindow->lockBuffer(nativeWindow, buffer);
 
     DEFINE_AND_VALIDATE_HOST_CONNECTION(EGL_FALSE);
     rcSurface = rcEnc->rcCreateWindowSurface(rcEnc, (uint32_t)config,
@@ -300,7 +299,7 @@
         rcEnc->rcDestroyWindowSurface(rcEnc, rcSurface);
     }
     if (buffer) {
-        nativeWindow->cancelBuffer(nativeWindow, buffer);
+        nativeWindow->cancelBuffer_DEPRECATED(nativeWindow, buffer);
     }
     nativeWindow->common.decRef(&nativeWindow->common);
 }
@@ -316,12 +315,11 @@
 
     rcEnc->rcFlushWindowColorBuffer(rcEnc, rcSurface);
 
-    nativeWindow->queueBuffer(nativeWindow, buffer);
-    if (nativeWindow->dequeueBuffer(nativeWindow, &buffer)) {
+    nativeWindow->queueBuffer_DEPRECATED(nativeWindow, buffer);
+    if (nativeWindow->dequeueBuffer_DEPRECATED(nativeWindow, &buffer)) {
         buffer = NULL;
         setErrorReturn(EGL_BAD_ALLOC, EGL_FALSE);
     }
-    nativeWindow->lockBuffer(nativeWindow, buffer);
 
     rcEnc->rcSetWindowColorBuffer(rcEnc, rcSurface,
             ((cb_handle_t *)(buffer->handle))->hostHandle);
@@ -1148,7 +1146,9 @@
     if (native_buffer->common.version != sizeof(android_native_buffer_t))
         setErrorReturn(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
 
-    switch (native_buffer->format) {
+    cb_handle_t *cb = (cb_handle_t *)(native_buffer->handle);
+
+    switch (cb->format) {
         case HAL_PIXEL_FORMAT_RGBA_8888:
         case HAL_PIXEL_FORMAT_RGBX_8888:
         case HAL_PIXEL_FORMAT_RGB_888:
diff --git a/tools/emulator/opengl/system/gralloc/gralloc.cpp b/tools/emulator/opengl/system/gralloc/gralloc.cpp
index 4334835..ac20d49 100644
--- a/tools/emulator/opengl/system/gralloc/gralloc.cpp
+++ b/tools/emulator/opengl/system/gralloc/gralloc.cpp
@@ -133,8 +133,11 @@
     D("gralloc_alloc w=%d h=%d usage=0x%x\n", w, h, usage);
 
     gralloc_device_t *grdev = (gralloc_device_t *)dev;
-    if (!grdev || !pHandle || !pStride)
+    if (!grdev || !pHandle || !pStride) {
+        ALOGE("gralloc_alloc: Bad inputs (grdev: %p, pHandle: %p, pStride: %p",
+                grdev, pHandle, pStride);
         return -EINVAL;
+    }
 
     //
     // Validate usage: buffer cannot be written both by s/w and h/w access.
@@ -142,9 +145,42 @@
     bool sw_write = (0 != (usage & GRALLOC_USAGE_SW_WRITE_MASK));
     bool hw_write = (usage & GRALLOC_USAGE_HW_RENDER);
     if (hw_write && sw_write) {
+        ALOGE("gralloc_alloc: Mismatched usage flags: %d x %d, usage %x",
+                w, h, usage);
         return -EINVAL;
     }
     bool sw_read = (0 != (usage & GRALLOC_USAGE_SW_READ_MASK));
+    bool hw_cam_write = usage & GRALLOC_USAGE_HW_CAMERA_WRITE;
+    bool hw_cam_read = usage & GRALLOC_USAGE_HW_CAMERA_READ;
+    bool hw_vid_enc_read = usage & GRALLOC_USAGE_HW_VIDEO_ENCODER;
+
+    // Pick the right concrete pixel format given the endpoints as encoded in
+    // the usage bits.  Every end-point pair needs explicit listing here.
+    if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+        // Camera as producer
+        if (usage & GRALLOC_USAGE_HW_CAMERA_WRITE) {
+            if (usage & GRALLOC_USAGE_HW_TEXTURE) {
+                // Camera-to-display is RGBA
+                format = HAL_PIXEL_FORMAT_RGBA_8888;
+            } else if (usage & GRALLOC_USAGE_HW_VIDEO_ENCODER) {
+                // Camera-to-encoder is NV21
+                format = HAL_PIXEL_FORMAT_YCrCb_420_SP;
+            } else if ((usage & GRALLOC_USAGE_HW_CAMERA_MASK) ==
+                    GRALLOC_USAGE_HW_CAMERA_ZSL) {
+                // Camera-to-ZSL-queue is RGB_888
+                format = HAL_PIXEL_FORMAT_RGB_888;
+            }
+        }
+
+        if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+            ALOGE("gralloc_alloc: Requested auto format selection, "
+                    "but no known format for this usage: %d x %d, usage %x",
+                    w, h, usage);
+            return -EINVAL;
+        }
+    }
+
+    bool yuv_format = false;
 
     int ashmem_size = 0;
     int stride = w;
@@ -185,14 +221,38 @@
         case HAL_PIXEL_FORMAT_RAW_SENSOR:
             bpp = 2;
             align = 16*bpp;
-            if (! (sw_read && sw_write) ) {
-                // Raw sensor data cannot be used by HW
+            if (! ((sw_read || hw_cam_read) && (sw_write || hw_cam_write) ) ) {
+                // Raw sensor data only goes between camera and CPU
                 return -EINVAL;
             }
+            // Not expecting to actually create any GL surfaces for this
             glFormat = GL_LUMINANCE;
             glType = GL_UNSIGNED_SHORT;
             break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            bpp = 1;
+            if (! (sw_read && hw_cam_write) ) {
+                // Blob data cannot be used by HW other than camera emulator
+                return -EINVAL;
+            }
+            // Not expecting to actually create any GL surfaces for this
+            glFormat = GL_LUMINANCE;
+            glType = GL_UNSIGNED_BYTE;
+            break;
+        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+            align = 1;
+            bpp = 1; // per-channel bpp
+            yuv_format = true;
+            // Not expecting to actually create any GL surfaces for this
+            break;
+        case HAL_PIXEL_FORMAT_YV12:
+            align = 16;
+            bpp = 1; // per-channel bpp
+            yuv_format = true;
+            // Not expecting to actually create any GL surfaces for this
+            break;
         default:
+            ALOGE("gralloc_alloc: Unknown format %d", format);
             return -EINVAL;
     }
 
@@ -201,16 +261,24 @@
         ashmem_size += sizeof(uint32_t);
     }
 
-    if (sw_read || sw_write) {
+    if (sw_read || sw_write || hw_cam_write || hw_vid_enc_read) {
         // keep space for image on guest memory if SW access is needed
-
-        size_t bpr = (w*bpp + (align-1)) & ~(align-1);
-        ashmem_size += (bpr * h);
-        stride = bpr / bpp;
+        // or if the camera is doing writing
+        if (yuv_format) {
+            size_t yStride = (w*bpp + (align - 1)) & ~(align-1);
+            size_t uvStride = (yStride / 2 + (align - 1)) & ~(align-1);
+            size_t uvHeight = h / 2;
+            ashmem_size += yStride * h + 2 * (uvHeight * uvStride);
+            stride = yStride / bpp;
+        } else {
+            size_t bpr = (w*bpp + (align-1)) & ~(align-1);
+            ashmem_size += (bpr * h);
+            stride = bpr / bpp;
+        }
     }
 
-    D("gralloc_alloc ashmem_size=%d, stride=%d, tid %d\n", ashmem_size, stride,
-            gettid());
+    D("gralloc_alloc format=%d, ashmem_size=%d, stride=%d, tid %d\n", format,
+            ashmem_size, stride, gettid());
 
     //
     // Allocate space in ashmem if needed
@@ -229,7 +297,7 @@
     }
 
     cb_handle_t *cb = new cb_handle_t(fd, ashmem_size, usage,
-                                      w, h, glFormat, glType);
+                                      w, h, format, glFormat, glType);
 
     if (ashmem_size > 0) {
         //
@@ -248,8 +316,11 @@
 
     //
     // Allocate ColorBuffer handle on the host (only if h/w access is allowed)
+    // Only do this for some h/w usages, not all.
     //
-    if (usage & GRALLOC_USAGE_HW_MASK) {
+    if (usage & (GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER |
+                    GRALLOC_USAGE_HW_2D | GRALLOC_USAGE_HW_COMPOSER |
+                    GRALLOC_USAGE_HW_FB) ) {
         DEFINE_HOST_CONNECTION;
         if (hostCon && rcEnc) {
             cb->hostHandle = rcEnc->rcCreateColorBuffer(rcEnc, w, h, glFormat);
@@ -542,14 +613,20 @@
     bool sw_write = (0 != (usage & GRALLOC_USAGE_SW_WRITE_MASK));
     bool hw_read = (usage & GRALLOC_USAGE_HW_TEXTURE);
     bool hw_write = (usage & GRALLOC_USAGE_HW_RENDER);
+    bool hw_vid_enc_read = (usage & GRALLOC_USAGE_HW_VIDEO_ENCODER);
+    bool hw_cam_write = (usage & GRALLOC_USAGE_HW_CAMERA_WRITE);
+    bool hw_cam_read = (usage & GRALLOC_USAGE_HW_CAMERA_READ);
     bool sw_read_allowed = (0 != (cb->usage & GRALLOC_USAGE_SW_READ_MASK));
     bool sw_write_allowed = (0 != (cb->usage & GRALLOC_USAGE_SW_WRITE_MASK));
 
     if ( (hw_read || hw_write) ||
-         (!sw_read && !sw_write) ||
+         (!sw_read && !sw_write &&
+                 !hw_cam_write && !hw_cam_read &&
+                 !hw_vid_enc_read) ||
          (sw_read && !sw_read_allowed) ||
          (sw_write && !sw_write_allowed) ) {
-        ALOGE("gralloc_lock usage mismatch usage=0x%x cb->usage=0x%x\n", usage, cb->usage);
+        ALOGE("gralloc_lock usage mismatch usage=0x%x cb->usage=0x%x\n", usage,
+                cb->usage);
         return -EINVAL;
     }
 
@@ -559,7 +636,9 @@
     //
     // make sure ashmem area is mapped if needed
     //
-    if (cb->canBePosted() || sw_read || sw_write) {
+    if (cb->canBePosted() || sw_read || sw_write ||
+            hw_cam_write || hw_cam_read ||
+            hw_vid_enc_read) {
         if (cb->ashmemBasePid != getpid() || !cb->ashmemBase) {
             return -EACCES;
         }
@@ -596,11 +675,11 @@
     //
     // is virtual address required ?
     //
-    if (sw_read || sw_write) {
+    if (sw_read || sw_write || hw_cam_write || hw_cam_read || hw_vid_enc_read) {
         *vaddr = cpu_addr;
     }
 
-    if (sw_write) {
+    if (sw_write || hw_cam_write) {
         //
         // Keep locked region if locked for s/w write access.
         //
@@ -610,6 +689,9 @@
         cb->lockedHeight = h;
     }
 
+    DD("gralloc_lock success. vaddr: %p, *vaddr: %p, usage: %x, cpu_addr: %p",
+            vaddr, vaddr ? *vaddr : 0, usage, cpu_addr);
+
     return 0;
 }
 
diff --git a/tools/emulator/system/camera/Android.mk b/tools/emulator/system/camera/Android.mk
index c51f621..3843c1d 100755
--- a/tools/emulator/system/camera/Android.mk
+++ b/tools/emulator/system/camera/Android.mk
@@ -33,11 +33,13 @@
 	libjpeg \
 	libskia \
 	libandroid_runtime \
+	libcamera_metadata
 
 LOCAL_C_INCLUDES += external/jpeg \
 	external/skia/include/core/ \
 	frameworks/native/include/media/hardware \
 	frameworks/base/core/jni/android/graphics \
+	$(LOCAL_PATH)/../../opengl/system/OpenglSystemCommon \
 	$(call include-path-for, camera)
 
 LOCAL_SRC_FILES := \
@@ -57,7 +59,11 @@
 	JpegCompressor.cpp \
     EmulatedCamera2.cpp \
 	EmulatedFakeCamera2.cpp \
-	EmulatedQemuCamera2.cpp
+	EmulatedQemuCamera2.cpp \
+	fake-pipeline2/Scene.cpp \
+	fake-pipeline2/Sensor.cpp \
+	fake-pipeline2/JpegCompressor.cpp
+
 
 ifeq ($(TARGET_PRODUCT),vbox_x86)
 LOCAL_MODULE := camera.vbox_x86
diff --git a/tools/emulator/system/camera/EmulatedCamera2.cpp b/tools/emulator/system/camera/EmulatedCamera2.cpp
index f7672f4..bbc1740 100644
--- a/tools/emulator/system/camera/EmulatedCamera2.cpp
+++ b/tools/emulator/system/camera/EmulatedCamera2.cpp
@@ -22,7 +22,7 @@
  * for all camera API calls that defined by camera2_device_ops_t API.
  */
 
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "EmulatedCamera2_Camera"
 #include <cutils/log.h>
 
@@ -48,36 +48,10 @@
     ops = &sDeviceOps;
     priv = this;
 
-    mRequestQueueDstOps.notify_queue_not_empty =
-            EmulatedCamera2::request_queue_notify_queue_not_empty;
-    mRequestQueueDstOps.parent                 = this;
+    mNotifyCb = NULL;
 
-    mRequestQueueDstOps.notify_queue_not_empty =
-            EmulatedCamera2::reprocess_queue_notify_queue_not_empty;
-    mReprocessQueueDstOps.parent               = this;
-
-    mFrameQueueSrcOps.buffer_count = EmulatedCamera2::frame_queue_buffer_count;
-    mFrameQueueSrcOps.dequeue      = EmulatedCamera2::frame_queue_dequeue;
-    mFrameQueueSrcOps.free         = EmulatedCamera2::frame_queue_free;
-    mFrameQueueSrcOps.parent       = this;
-
-    mReprocessStreamOps.dequeue_buffer =
-            EmulatedCamera2::reprocess_stream_dequeue_buffer;
-    mReprocessStreamOps.enqueue_buffer =
-            EmulatedCamera2::reprocess_stream_enqueue_buffer;
-    mReprocessStreamOps.cancel_buffer =
-            EmulatedCamera2::reprocess_stream_cancel_buffer;
-    mReprocessStreamOps.set_buffer_count =
-            EmulatedCamera2::reprocess_stream_set_buffer_count;
-    mReprocessStreamOps.set_crop = EmulatedCamera2::reprocess_stream_set_crop;
-    mReprocessStreamOps.set_timestamp =
-            EmulatedCamera2::reprocess_stream_set_timestamp;
-    mReprocessStreamOps.set_usage = EmulatedCamera2::reprocess_stream_set_usage;
-    mReprocessStreamOps.get_min_undequeued_buffer_count =
-            EmulatedCamera2::reprocess_stream_get_min_undequeued_buffer_count;
-    mReprocessStreamOps.lock_buffer =
-            EmulatedCamera2::reprocess_stream_lock_buffer;
-    mReprocessStreamOps.parent   = this;
+    mRequestQueueSrc = NULL;
+    mFrameQueueDst = NULL;
 
     mVendorTagOps.get_camera_vendor_section_name =
             EmulatedCamera2::get_camera_vendor_section_name;
@@ -109,6 +83,7 @@
  ***************************************************************************/
 
 status_t EmulatedCamera2::connectCamera(hw_device_t** device) {
+    *device = &common;
     return NO_ERROR;
 }
 
@@ -117,126 +92,92 @@
 }
 
 status_t EmulatedCamera2::getCameraInfo(struct camera_info* info) {
-
     return EmulatedBaseCamera::getCameraInfo(info);
 }
 
 /****************************************************************************
- * Camera API implementation.
+ * Camera Device API implementation.
  * These methods are called from the camera API callback routines.
  ***************************************************************************/
 
 /** Request input queue */
 
-int EmulatedCamera2::setRequestQueueSrcOps(
-    camera2_metadata_queue_src_ops *request_queue_src_ops) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::requestQueueNotifyNotEmpty() {
-    return NO_ERROR;
-}
-
-/** Reprocessing input queue */
-
-int EmulatedCamera2::setReprocessQueueSrcOps(
-    camera2_metadata_queue_src_ops *reprocess_queue_src_ops) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessQueueNotifyNotEmpty() {
-    return NO_ERROR;
-}
-
-/** Frame output queue */
-
-int EmulatedCamera2::setFrameQueueDstOps(camera2_metadata_queue_dst_ops *frame_queue_dst_ops) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::frameQueueBufferCount() {
-    return NO_ERROR;
-}
-int EmulatedCamera2::frameQueueDequeue(camera_metadata_t **buffer) {
-    return NO_ERROR;
-}
-int EmulatedCamera2::frameQueueFree(camera_metadata_t *old_buffer) {
-    return NO_ERROR;
-}
-
-/** Notifications to application */
-int EmulatedCamera2::setNotifyCallback(camera2_notify_callback notify_cb) {
-    return NO_ERROR;
+int EmulatedCamera2::requestQueueNotify() {
+    return INVALID_OPERATION;
 }
 
 /** Count of requests in flight */
 int EmulatedCamera2::getInProgressCount() {
-    return NO_ERROR;
+    return INVALID_OPERATION;
 }
 
 /** Cancel all captures in flight */
 int EmulatedCamera2::flushCapturesInProgress() {
-    return NO_ERROR;
+    return INVALID_OPERATION;
 }
 
-/** Reprocessing input stream management */
-int EmulatedCamera2::reprocessStreamDequeueBuffer(buffer_handle_t** buffer,
-        int *stride) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamEnqueueBuffer(buffer_handle_t* buffer) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamCancelBuffer(buffer_handle_t* buffer) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamSetBufferCount(int count) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamSetCrop(int left, int top, int right, int bottom) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamSetTimestamp(int64_t timestamp) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamSetUsage(int usage) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamSetSwapInterval(int interval) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamGetMinUndequeuedBufferCount(int *count) {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::reprocessStreamLockBuffer(buffer_handle_t *buffer) {
-    return NO_ERROR;
+/** Construct a default request for a given use case */
+int EmulatedCamera2::constructDefaultRequest(
+        int request_template,
+        camera_metadata_t **request) {
+    return INVALID_OPERATION;
 }
 
 /** Output stream creation and management */
 
-int EmulatedCamera2::getStreamSlotCount() {
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::allocateStream(uint32_t stream_slot,
+int EmulatedCamera2::allocateStream(
         uint32_t width,
         uint32_t height,
         int format,
-        camera2_stream_ops_t *stream_ops) {
-    return NO_ERROR;
+        const camera2_stream_ops_t *stream_ops,
+        uint32_t *stream_id,
+        uint32_t *format_actual,
+        uint32_t *usage,
+        uint32_t *max_buffers) {
+    return INVALID_OPERATION;
 }
 
-int EmulatedCamera2::releaseStream(uint32_t stream_slot) {
-    return NO_ERROR;
+int EmulatedCamera2::registerStreamBuffers(
+        uint32_t stream_id,
+        int num_buffers,
+        buffer_handle_t *buffers) {
+    return INVALID_OPERATION;
+}
+
+
+int EmulatedCamera2::releaseStream(uint32_t stream_id) {
+    return INVALID_OPERATION;
+}
+
+/** Reprocessing input stream management */
+
+int EmulatedCamera2::allocateReprocessStream(
+        uint32_t width,
+        uint32_t height,
+        uint32_t format,
+        const camera2_stream_in_ops_t *reprocess_stream_ops,
+        uint32_t *stream_id,
+        uint32_t *consumer_usage,
+        uint32_t *max_buffers) {
+    return INVALID_OPERATION;
+}
+
+int EmulatedCamera2::allocateReprocessStreamFromStream(
+        uint32_t output_stream_id,
+        const camera2_stream_in_ops_t *reprocess_stream_ops,
+        uint32_t *stream_id) {
+    return INVALID_OPERATION;
+}
+
+int EmulatedCamera2::releaseReprocessStream(uint32_t stream_id) {
+    return INVALID_OPERATION;
+}
+
+/** 3A triggering */
+
+int EmulatedCamera2::triggerAction(uint32_t trigger_id,
+                                   int ext1, int ext2) {
+    return INVALID_OPERATION;
 }
 
 /** Custom tag query methods */
@@ -253,14 +194,10 @@
     return -1;
 }
 
-/** Shutdown and debug methods */
-
-int EmulatedCamera2::release() {
-    return NO_ERROR;
-}
+/** Debug methods */
 
 int EmulatedCamera2::dump(int fd) {
-    return NO_ERROR;
+    return INVALID_OPERATION;
 }
 
 /****************************************************************************
@@ -272,214 +209,129 @@
  * hardware/libhardware/include/hardware/camera2.h for information on each
  * of these callbacks. Implemented in this class, these callbacks simply
  * dispatch the call into an instance of EmulatedCamera2 class defined by the
- * 'camera_device2' parameter.
+ * 'camera_device2' parameter, or set a member value in the same.
  ***************************************************************************/
 
-int EmulatedCamera2::set_request_queue_src_ops(struct camera2_device *d,
-        camera2_metadata_queue_src_ops *queue_src_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    return ec->setRequestQueueSrcOps(queue_src_ops);
+EmulatedCamera2* getInstance(const camera2_device_t *d) {
+    const EmulatedCamera2* cec = static_cast<const EmulatedCamera2*>(d);
+    return const_cast<EmulatedCamera2*>(cec);
 }
 
-int EmulatedCamera2::get_request_queue_dst_ops(struct camera2_device *d,
-        camera2_metadata_queue_dst_ops **queue_dst_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    *queue_dst_ops = static_cast<camera2_metadata_queue_dst_ops*>(
-        &ec->mRequestQueueDstOps);
+int EmulatedCamera2::set_request_queue_src_ops(const camera2_device_t *d,
+        const camera2_request_queue_src_ops *queue_src_ops) {
+    EmulatedCamera2* ec = getInstance(d);
+    ec->mRequestQueueSrc = queue_src_ops;
     return NO_ERROR;
 }
 
-int EmulatedCamera2::request_queue_notify_queue_not_empty(
-        camera2_metadata_queue_dst_ops *q) {
-    EmulatedCamera2* ec = static_cast<QueueDstOps*>(q)->parent;
-    return ec->requestQueueNotifyNotEmpty();
+int EmulatedCamera2::notify_request_queue_not_empty(const camera2_device_t *d) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->requestQueueNotify();
 }
 
-int EmulatedCamera2::set_reprocess_queue_src_ops(struct camera2_device *d,
-        camera2_metadata_queue_src_ops *queue_src_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    return ec->setReprocessQueueSrcOps(queue_src_ops);
-}
-
-int EmulatedCamera2::get_reprocess_queue_dst_ops(struct camera2_device *d,
-        camera2_metadata_queue_dst_ops **queue_dst_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    *queue_dst_ops = static_cast<camera2_metadata_queue_dst_ops*>(
-        &ec->mReprocessQueueDstOps);
+int EmulatedCamera2::set_frame_queue_dst_ops(const camera2_device_t *d,
+        const camera2_frame_queue_dst_ops *queue_dst_ops) {
+    EmulatedCamera2* ec = getInstance(d);
+    ec->mFrameQueueDst = queue_dst_ops;
     return NO_ERROR;
 }
 
-int EmulatedCamera2::reprocess_queue_notify_queue_not_empty(
-        camera2_metadata_queue_dst_ops *q) {
-    EmulatedCamera2* ec = static_cast<QueueDstOps*>(q)->parent;
-    return ec->reprocessQueueNotifyNotEmpty();
-}
-
-int EmulatedCamera2::set_frame_queue_dst_ops(struct camera2_device *d,
-        camera2_metadata_queue_dst_ops *queue_dst_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    return ec->setFrameQueueDstOps(queue_dst_ops);
-}
-
-int EmulatedCamera2::get_frame_queue_src_ops(struct camera2_device *d,
-        camera2_metadata_queue_src_ops **queue_src_ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    *queue_src_ops = static_cast<camera2_metadata_queue_src_ops*>(
-        &ec->mFrameQueueSrcOps);
-    return NO_ERROR;
-}
-
-int EmulatedCamera2::frame_queue_buffer_count(camera2_metadata_queue_src_ops *q) {
-    EmulatedCamera2 *ec = static_cast<QueueSrcOps*>(q)->parent;
-    return ec->frameQueueBufferCount();
-}
-
-int EmulatedCamera2::frame_queue_dequeue(camera2_metadata_queue_src_ops *q,
-        camera_metadata_t **buffer) {
-    EmulatedCamera2 *ec = static_cast<QueueSrcOps*>(q)->parent;
-    return ec->frameQueueDequeue(buffer);
-}
-
-int EmulatedCamera2::frame_queue_free(camera2_metadata_queue_src_ops *q,
-        camera_metadata_t *old_buffer) {
-    EmulatedCamera2 *ec = static_cast<QueueSrcOps*>(q)->parent;
-    return ec->frameQueueFree(old_buffer);
-}
-
-int EmulatedCamera2::set_notify_callback(struct camera2_device *d,
-        camera2_notify_callback notify_cb) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    return ec->setNotifyCallback(notify_cb);
-}
-
-int EmulatedCamera2::get_in_progress_count(struct camera2_device *d) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
+int EmulatedCamera2::get_in_progress_count(const camera2_device_t *d) {
+    EmulatedCamera2* ec = getInstance(d);
     return ec->getInProgressCount();
 }
 
-int EmulatedCamera2::flush_captures_in_progress(struct camera2_device *d) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
+int EmulatedCamera2::flush_captures_in_progress(const camera2_device_t *d) {
+    EmulatedCamera2* ec = getInstance(d);
     return ec->flushCapturesInProgress();
 }
 
-int EmulatedCamera2::get_reprocess_stream_ops(camera2_device_t *d,
-        camera2_stream_ops **stream) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
-    *stream = static_cast<camera2_stream_ops*>(&ec->mReprocessStreamOps);
-    return NO_ERROR;
+int EmulatedCamera2::construct_default_request(const camera2_device_t *d,
+        int request_template,
+        camera_metadata_t **request) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->constructDefaultRequest(request_template, request);
 }
 
-int EmulatedCamera2::reprocess_stream_dequeue_buffer(camera2_stream_ops *s,
-        buffer_handle_t** buffer, int *stride) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamDequeueBuffer(buffer, stride);
+int EmulatedCamera2::allocate_stream(const camera2_device_t *d,
+        uint32_t width,
+        uint32_t height,
+        int format,
+        const camera2_stream_ops_t *stream_ops,
+        uint32_t *stream_id,
+        uint32_t *format_actual,
+        uint32_t *usage,
+        uint32_t *max_buffers) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->allocateStream(width, height, format, stream_ops,
+            stream_id, format_actual, usage, max_buffers);
 }
 
-int EmulatedCamera2::reprocess_stream_enqueue_buffer(camera2_stream_ops *s,
-        buffer_handle_t* buffer) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamEnqueueBuffer(buffer);
+int EmulatedCamera2::register_stream_buffers(const camera2_device_t *d,
+        uint32_t stream_id,
+        int num_buffers,
+        buffer_handle_t *buffers) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->registerStreamBuffers(stream_id,
+            num_buffers,
+            buffers);
+}
+int EmulatedCamera2::release_stream(const camera2_device_t *d,
+        uint32_t stream_id) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->releaseStream(stream_id);
 }
 
-int EmulatedCamera2::reprocess_stream_cancel_buffer(camera2_stream_ops *s,
-        buffer_handle_t* buffer) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamCancelBuffer(buffer);
-}
-
-int EmulatedCamera2::reprocess_stream_set_buffer_count(camera2_stream_ops *s,
-        int count) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamSetBufferCount(count);
-}
-
-int EmulatedCamera2::reprocess_stream_set_crop(camera2_stream_ops *s,
-        int left, int top, int right, int bottom) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamSetCrop(left, top, right, bottom);
-}
-
-int EmulatedCamera2::reprocess_stream_set_timestamp(camera2_stream_ops *s,
-        int64_t timestamp) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamSetTimestamp(timestamp);
-}
-
-int EmulatedCamera2::reprocess_stream_set_usage(camera2_stream_ops *s,
-        int usage) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamSetUsage(usage);
-}
-
-int EmulatedCamera2::reprocess_stream_set_swap_interval(camera2_stream_ops *s,
-        int interval) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamSetSwapInterval(interval);
-}
-
-int EmulatedCamera2::reprocess_stream_get_min_undequeued_buffer_count(
-        const camera2_stream_ops *s,
-        int *count) {
-    EmulatedCamera2* ec = static_cast<const StreamOps*>(s)->parent;
-    return ec->reprocessStreamGetMinUndequeuedBufferCount(count);
-}
-
-int EmulatedCamera2::reprocess_stream_lock_buffer(camera2_stream_ops *s,
-        buffer_handle_t* buffer) {
-    EmulatedCamera2* ec = static_cast<StreamOps*>(s)->parent;
-    return ec->reprocessStreamLockBuffer(buffer);
-}
-
-int EmulatedCamera2::get_stream_slot_count(struct camera2_device *d) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(d);
-    return ec->getStreamSlotCount();
-}
-
-int EmulatedCamera2::allocate_stream(struct camera2_device *d,
-        uint32_t stream_slot,
+int EmulatedCamera2::allocate_reprocess_stream(const camera2_device_t *d,
         uint32_t width,
         uint32_t height,
         uint32_t format,
-        camera2_stream_ops_t *stream_ops) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(d);
-    return ec->allocateStream(stream_slot, width, height, format, stream_ops);
+        const camera2_stream_in_ops_t *reprocess_stream_ops,
+        uint32_t *stream_id,
+        uint32_t *consumer_usage,
+        uint32_t *max_buffers) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->allocateReprocessStream(width, height, format,
+            reprocess_stream_ops, stream_id, consumer_usage, max_buffers);
 }
 
-int EmulatedCamera2::release_stream(struct camera2_device *d,
-        uint32_t stream_slot) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(d);
-    return ec->releaseStream(stream_slot);
+int EmulatedCamera2::allocate_reprocess_stream_from_stream(
+            const camera2_device_t *d,
+            uint32_t output_stream_id,
+            const camera2_stream_in_ops_t *reprocess_stream_ops,
+            uint32_t *stream_id) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->allocateReprocessStreamFromStream(output_stream_id,
+            reprocess_stream_ops, stream_id);
 }
 
-void EmulatedCamera2::release(struct camera2_device *d) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(d);
-    ec->release();
+
+int EmulatedCamera2::release_reprocess_stream(const camera2_device_t *d,
+        uint32_t stream_id) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->releaseReprocessStream(stream_id);
 }
 
-int EmulatedCamera2::dump(struct camera2_device *d, int fd) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(d);
-    return ec->dump(fd);
+int EmulatedCamera2::trigger_action(const camera2_device_t *d,
+        uint32_t trigger_id,
+        int ext1,
+        int ext2) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->triggerAction(trigger_id, ext1, ext2);
 }
 
-int EmulatedCamera2::close(struct hw_device_t* device) {
-    EmulatedCamera2* ec =
-            static_cast<EmulatedCamera2*>(
-                reinterpret_cast<struct camera2_device*>(device) );
-    if (ec == NULL) {
-        ALOGE("%s: Unexpected NULL camera2 device", __FUNCTION__);
-        return -EINVAL;
-    }
-    return ec->closeCamera();
+int EmulatedCamera2::set_notify_callback(const camera2_device_t *d,
+        camera2_notify_callback notify_cb, void* user) {
+    EmulatedCamera2* ec = getInstance(d);
+    Mutex::Autolock l(ec->mMutex);
+    ec->mNotifyCb = notify_cb;
+    ec->mNotifyUserPtr = user;
+    return NO_ERROR;
 }
 
-int EmulatedCamera2::get_metadata_vendor_tag_ops(struct camera2_device *d,
+int EmulatedCamera2::get_metadata_vendor_tag_ops(const camera2_device_t *d,
         vendor_tag_query_ops_t **ops) {
-    EmulatedCamera2* ec = static_cast<EmulatedCamera2*>(d);
+    EmulatedCamera2* ec = getInstance(d);
     *ops = static_cast<vendor_tag_query_ops_t*>(
             &ec->mVendorTagOps);
     return NO_ERROR;
@@ -506,22 +358,50 @@
     return ec->getVendorTagType(tag);
 }
 
+int EmulatedCamera2::dump(const camera2_device_t *d, int fd) {
+    EmulatedCamera2* ec = getInstance(d);
+    return ec->dump(fd);
+}
+
+int EmulatedCamera2::close(struct hw_device_t* device) {
+    EmulatedCamera2* ec =
+            static_cast<EmulatedCamera2*>(
+                reinterpret_cast<camera2_device_t*>(device) );
+    if (ec == NULL) {
+        ALOGE("%s: Unexpected NULL camera2 device", __FUNCTION__);
+        return -EINVAL;
+    }
+    return ec->closeCamera();
+}
+
+void EmulatedCamera2::sendNotification(int32_t msgType,
+        int32_t ext1, int32_t ext2, int32_t ext3) {
+    camera2_notify_callback notifyCb;
+    {
+        Mutex::Autolock l(mMutex);
+        notifyCb = mNotifyCb;
+    }
+    if (notifyCb != NULL) {
+        notifyCb(msgType, ext1, ext2, ext3, mNotifyUserPtr);
+    }
+}
+
 camera2_device_ops_t EmulatedCamera2::sDeviceOps = {
     EmulatedCamera2::set_request_queue_src_ops,
-    EmulatedCamera2::get_request_queue_dst_ops,
-    EmulatedCamera2::set_reprocess_queue_src_ops,
-    EmulatedCamera2::get_reprocess_queue_dst_ops,
+    EmulatedCamera2::notify_request_queue_not_empty,
     EmulatedCamera2::set_frame_queue_dst_ops,
-    EmulatedCamera2::get_frame_queue_src_ops,
-    EmulatedCamera2::set_notify_callback,
     EmulatedCamera2::get_in_progress_count,
     EmulatedCamera2::flush_captures_in_progress,
-    EmulatedCamera2::get_reprocess_stream_ops,
-    EmulatedCamera2::get_stream_slot_count,
+    EmulatedCamera2::construct_default_request,
     EmulatedCamera2::allocate_stream,
+    EmulatedCamera2::register_stream_buffers,
     EmulatedCamera2::release_stream,
+    EmulatedCamera2::allocate_reprocess_stream,
+    EmulatedCamera2::allocate_reprocess_stream_from_stream,
+    EmulatedCamera2::release_reprocess_stream,
+    EmulatedCamera2::trigger_action,
+    EmulatedCamera2::set_notify_callback,
     EmulatedCamera2::get_metadata_vendor_tag_ops,
-    EmulatedCamera2::release,
     EmulatedCamera2::dump
 };
 
diff --git a/tools/emulator/system/camera/EmulatedCamera2.h b/tools/emulator/system/camera/EmulatedCamera2.h
index feeadf9..755ed0e 100644
--- a/tools/emulator/system/camera/EmulatedCamera2.h
+++ b/tools/emulator/system/camera/EmulatedCamera2.h
@@ -28,6 +28,8 @@
 #include "hardware/camera2.h"
 #include "system/camera_metadata.h"
 #include "EmulatedBaseCamera.h"
+#include <utils/Thread.h>
+#include <utils/Mutex.h>
 
 namespace android {
 
@@ -67,7 +69,7 @@
     virtual status_t Initialize();
 
     /****************************************************************************
-     * Camera API implementation
+     * Camera module API and generic hardware device API implementation
      ***************************************************************************/
 
 public:
@@ -75,7 +77,7 @@
 
     virtual status_t closeCamera();
 
-    virtual status_t getCameraInfo(struct camera_info* info);
+    virtual status_t getCameraInfo(struct camera_info* info) = 0;
 
     /****************************************************************************
      * Camera API implementation.
@@ -83,177 +85,144 @@
      ***************************************************************************/
 
 protected:
-    /** Request input queue */
-
-    int setRequestQueueSrcOps(
-        camera2_metadata_queue_src_ops *request_queue_src_ops);
-
-    int requestQueueNotifyNotEmpty();
-
-    /** Reprocessing input queue */
-
-    int setReprocessQueueSrcOps(
-        camera2_metadata_queue_src_ops *reprocess_queue_src_ops);
-
-    int reprocessQueueNotifyNotEmpty();
-
-    /** Frame output queue */
-
-    int setFrameQueueDstOps(camera2_metadata_queue_dst_ops *frame_queue_dst_ops);
-
-    int frameQueueBufferCount();
-    int frameQueueDequeue(camera_metadata_t **buffer);
-    int frameQueueFree(camera_metadata_t *old_buffer);
-
-    /** Notifications to application */
-    int setNotifyCallback(camera2_notify_callback notify_cb);
+    /** Request input queue notification */
+    virtual int requestQueueNotify();
 
     /** Count of requests in flight */
-    int getInProgressCount();
+    virtual int getInProgressCount();
 
     /** Cancel all captures in flight */
-    int flushCapturesInProgress();
+    virtual int flushCapturesInProgress();
 
-    /** Reprocessing input stream management */
-    int reprocessStreamDequeueBuffer(buffer_handle_t** buffer,
-            int *stride);
-
-    int reprocessStreamEnqueueBuffer(buffer_handle_t* buffer);
-
-    int reprocessStreamCancelBuffer(buffer_handle_t* buffer);
-
-    int reprocessStreamSetBufferCount(int count);
-
-    int reprocessStreamSetCrop(int left, int top, int right, int bottom);
-
-    int reprocessStreamSetTimestamp(int64_t timestamp);
-
-    int reprocessStreamSetUsage(int usage);
-
-    int reprocessStreamSetSwapInterval(int interval);
-
-    int reprocessStreamGetMinUndequeuedBufferCount(int *count);
-
-    int reprocessStreamLockBuffer(buffer_handle_t *buffer);
+    virtual int constructDefaultRequest(
+        int request_template,
+        camera_metadata_t **request);
 
     /** Output stream creation and management */
-
-    int getStreamSlotCount();
-
-    int allocateStream(uint32_t stream_slot,
+    virtual int allocateStream(
             uint32_t width,
             uint32_t height,
             int format,
-            camera2_stream_ops_t *stream_ops);
+            const camera2_stream_ops_t *stream_ops,
+            uint32_t *stream_id,
+            uint32_t *format_actual,
+            uint32_t *usage,
+            uint32_t *max_buffers);
 
-    int releaseStream(uint32_t stream_slot);
+    virtual int registerStreamBuffers(
+            uint32_t stream_id,
+            int num_buffers,
+            buffer_handle_t *buffers);
+
+    virtual int releaseStream(uint32_t stream_id);
+
+    /** Input stream creation and management */
+    virtual int allocateReprocessStream(
+            uint32_t width,
+            uint32_t height,
+            uint32_t format,
+            const camera2_stream_in_ops_t *reprocess_stream_ops,
+            uint32_t *stream_id,
+            uint32_t *consumer_usage,
+            uint32_t *max_buffers);
+
+    virtual int allocateReprocessStreamFromStream(
+            uint32_t output_stream_id,
+            const camera2_stream_in_ops_t *reprocess_stream_ops,
+            uint32_t *stream_id);
+
+    virtual int releaseReprocessStream(uint32_t stream_id);
+
+    /** 3A action triggering */
+    virtual int triggerAction(uint32_t trigger_id,
+            int32_t ext1, int32_t ext2);
 
     /** Custom tag definitions */
-    const char* getVendorSectionName(uint32_t tag);
-    const char* getVendorTagName(uint32_t tag);
-    int         getVendorTagType(uint32_t tag);
+    virtual const char* getVendorSectionName(uint32_t tag);
+    virtual const char* getVendorTagName(uint32_t tag);
+    virtual int         getVendorTagType(uint32_t tag);
 
-    /** Shutdown and debug methods */
+    /** Debug methods */
 
-    int release();
-
-    int dump(int fd);
-
-    int close();
+    virtual int dump(int fd);
 
     /****************************************************************************
      * Camera API callbacks as defined by camera2_device_ops structure.  See
      * hardware/libhardware/include/hardware/camera2.h for information on each
      * of these callbacks. Implemented in this class, these callbacks simply
-     * dispatch the call into an instance of EmulatedCamera2 class defined in the
-     * 'camera_device2' parameter.
+     * dispatch the call into an instance of EmulatedCamera2 class defined in
+     * the 'camera_device2' parameter.
      ***************************************************************************/
 
 private:
     /** Input request queue */
-    static int set_request_queue_src_ops(camera2_device_t *,
-            camera2_metadata_queue_src_ops *queue_src_ops);
-    static int get_request_queue_dst_ops(camera2_device_t *,
-            camera2_metadata_queue_dst_ops **queue_dst_ops);
-    // for get_request_queue_dst_ops
-    static int request_queue_notify_queue_not_empty(
-        camera2_metadata_queue_dst_ops *);
-
-    /** Input reprocess queue */
-    static int set_reprocess_queue_src_ops(camera2_device_t *,
-            camera2_metadata_queue_src_ops *reprocess_queue_src_ops);
-    static int get_reprocess_queue_dst_ops(camera2_device_t *,
-            camera2_metadata_queue_dst_ops **queue_dst_ops);
-    // for reprocess_queue_dst_ops
-    static int reprocess_queue_notify_queue_not_empty(
-            camera2_metadata_queue_dst_ops *);
+    static int set_request_queue_src_ops(const camera2_device_t *,
+            const camera2_request_queue_src_ops *queue_src_ops);
+    static int notify_request_queue_not_empty(const camera2_device_t *);
 
     /** Output frame queue */
-    static int set_frame_queue_dst_ops(camera2_device_t *,
-            camera2_metadata_queue_dst_ops *queue_dst_ops);
-    static int get_frame_queue_src_ops(camera2_device_t *,
-            camera2_metadata_queue_src_ops **queue_src_ops);
-    // for get_frame_queue_src_ops
-    static int frame_queue_buffer_count(camera2_metadata_queue_src_ops *);
-    static int frame_queue_dequeue(camera2_metadata_queue_src_ops *,
-            camera_metadata_t **buffer);
-    static int frame_queue_free(camera2_metadata_queue_src_ops *,
-            camera_metadata_t *old_buffer);
-
-    /** Notifications to application */
-    static int set_notify_callback(camera2_device_t *,
-            camera2_notify_callback notify_cb);
+    static int set_frame_queue_dst_ops(const camera2_device_t *,
+            const camera2_frame_queue_dst_ops *queue_dst_ops);
 
     /** In-progress request management */
-    static int get_in_progress_count(camera2_device_t *);
+    static int get_in_progress_count(const camera2_device_t *);
 
-    static int flush_captures_in_progress(camera2_device_t *);
+    static int flush_captures_in_progress(const camera2_device_t *);
 
-    /** Input reprocessing stream */
-    static int get_reprocess_stream_ops(camera2_device_t *,
-            camera2_stream_ops_t **stream);
-    // for get_reprocess_stream_ops
-    static int reprocess_stream_dequeue_buffer(camera2_stream_ops *,
-            buffer_handle_t** buffer, int *stride);
-    static int reprocess_stream_enqueue_buffer(camera2_stream_ops *,
-            buffer_handle_t* buffer);
-    static int reprocess_stream_cancel_buffer(camera2_stream_ops *,
-            buffer_handle_t* buffer);
-    static int reprocess_stream_set_buffer_count(camera2_stream_ops *,
-            int count);
-    static int reprocess_stream_set_crop(camera2_stream_ops *,
-            int left, int top, int right, int bottom);
-    static int reprocess_stream_set_timestamp(camera2_stream_ops *,
-            int64_t timestamp);
-    static int reprocess_stream_set_usage(camera2_stream_ops *,
-            int usage);
-    static int reprocess_stream_set_swap_interval(camera2_stream_ops *,
-            int interval);
-    static int reprocess_stream_get_min_undequeued_buffer_count(
-            const camera2_stream_ops *,
-            int *count);
-    static int reprocess_stream_lock_buffer(camera2_stream_ops *,
-            buffer_handle_t* buffer);
+    /** Request template creation */
+    static int construct_default_request(const camera2_device_t *,
+            int request_template,
+            camera_metadata_t **request);
 
-    /** Output stream allocation and management */
+    /** Stream management */
+    static int allocate_stream(const camera2_device_t *,
+            uint32_t width,
+            uint32_t height,
+            int format,
+            const camera2_stream_ops_t *stream_ops,
+            uint32_t *stream_id,
+            uint32_t *format_actual,
+            uint32_t *usage,
+            uint32_t *max_buffers);
 
-    static int get_stream_slot_count(camera2_device_t *);
+    static int register_stream_buffers(const camera2_device_t *,
+            uint32_t stream_id,
+            int num_buffers,
+            buffer_handle_t *buffers);
 
-    static int allocate_stream(camera2_device_t *,
-            uint32_t stream_slot,
+    static int release_stream(const camera2_device_t *,
+            uint32_t stream_id);
+
+    static int allocate_reprocess_stream(const camera2_device_t *,
             uint32_t width,
             uint32_t height,
             uint32_t format,
-            camera2_stream_ops_t *stream_ops);
+            const camera2_stream_in_ops_t *reprocess_stream_ops,
+            uint32_t *stream_id,
+            uint32_t *consumer_usage,
+            uint32_t *max_buffers);
 
-    static int release_stream(camera2_device_t *,
-            uint32_t stream_slot);
+    static int allocate_reprocess_stream_from_stream(const camera2_device_t *,
+            uint32_t output_stream_id,
+            const camera2_stream_in_ops_t *reprocess_stream_ops,
+            uint32_t *stream_id);
 
-    static void release(camera2_device_t *);
+    static int release_reprocess_stream(const camera2_device_t *,
+            uint32_t stream_id);
+
+    /** 3A triggers*/
+    static int trigger_action(const camera2_device_t *,
+            uint32_t trigger_id,
+            int ext1,
+            int ext2);
+
+    /** Notifications to application */
+    static int set_notify_callback(const camera2_device_t *,
+            camera2_notify_callback notify_cb,
+            void *user);
 
     /** Vendor metadata registration */
-
-    static int get_metadata_vendor_tag_ops(camera2_device_t *,
+    static int get_metadata_vendor_tag_ops(const camera2_device_t *,
             vendor_tag_query_ops_t **ops);
     // for get_metadata_vendor_tag_ops
     static const char* get_camera_vendor_section_name(
@@ -266,38 +235,36 @@
             const vendor_tag_query_ops_t *,
             uint32_t tag);
 
-    static int dump(camera2_device_t *, int fd);
+    static int dump(const camera2_device_t *, int fd);
 
+    /** For hw_device_t ops */
     static int close(struct hw_device_t* device);
 
     /****************************************************************************
-     * Data members
+     * Data members shared with implementations
      ***************************************************************************/
+  protected:
+    /** Mutex for calls through camera2 device interface */
+    Mutex mMutex;
 
-  private:
-    static camera2_device_ops_t sDeviceOps;
-
-    struct QueueDstOps : public camera2_metadata_queue_dst_ops {
-        EmulatedCamera2 *parent;
-    };
-
-    struct QueueSrcOps : public camera2_metadata_queue_src_ops {
-        EmulatedCamera2 *parent;
-    };
-
-    struct StreamOps : public camera2_stream_ops {
-        EmulatedCamera2 *parent;
-    };
+    const camera2_request_queue_src_ops *mRequestQueueSrc;
+    const camera2_frame_queue_dst_ops *mFrameQueueDst;
 
     struct TagOps : public vendor_tag_query_ops {
         EmulatedCamera2 *parent;
     };
-
-    QueueDstOps mRequestQueueDstOps;
-    QueueDstOps mReprocessQueueDstOps;
-    QueueSrcOps mFrameQueueSrcOps;
-    StreamOps   mReprocessStreamOps;
     TagOps      mVendorTagOps;
+
+    void sendNotification(int32_t msgType,
+            int32_t ext1, int32_t ext2, int32_t ext3);
+
+    /****************************************************************************
+     * Data members
+     ***************************************************************************/
+  private:
+    static camera2_device_ops_t sDeviceOps;
+    camera2_notify_callback mNotifyCb;
+    void* mNotifyUserPtr;
 };
 
 }; /* namespace android */
diff --git a/tools/emulator/system/camera/EmulatedCameraFactory.cpp b/tools/emulator/system/camera/EmulatedCameraFactory.cpp
index 2960751..84248ca 100755
--- a/tools/emulator/system/camera/EmulatedCameraFactory.cpp
+++ b/tools/emulator/system/camera/EmulatedCameraFactory.cpp
@@ -19,7 +19,7 @@
  * available for emulation.
  */
 
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "EmulatedCamera_Factory"
 #include <cutils/log.h>
 #include <cutils/properties.h>
@@ -44,6 +44,7 @@
           mFakeCameraNum(0),
           mConstructedOK(false)
 {
+    status_t res;
     /* Connect to the factory service in the emulator, and create Qemu cameras. */
     if (mQemuClient.connectClient(NULL) == NO_ERROR) {
         /* Connection has succeeded. Create emulated cameras for each camera
@@ -75,11 +76,13 @@
         switch (getBackCameraHalVersion()) {
             case 1:
                 mEmulatedCameras[camera_id] =
-                        new EmulatedFakeCamera(camera_id, false, &HAL_MODULE_INFO_SYM.common);
+                        new EmulatedFakeCamera(camera_id, true,
+                                &HAL_MODULE_INFO_SYM.common);
                 break;
             case 2:
                 mEmulatedCameras[camera_id] =
-                        new EmulatedFakeCamera2(camera_id, false, &HAL_MODULE_INFO_SYM.common);
+                        new EmulatedFakeCamera2(camera_id, true,
+                                &HAL_MODULE_INFO_SYM.common);
                 break;
             default:
                 ALOGE("%s: Unknown back camera hal version requested: %d", __FUNCTION__,
@@ -88,12 +91,15 @@
         if (mEmulatedCameras[camera_id] != NULL) {
             ALOGV("%s: Back camera device version is %d", __FUNCTION__,
                     getBackCameraHalVersion());
-            if (mEmulatedCameras[camera_id]->Initialize() != NO_ERROR) {
+            res = mEmulatedCameras[camera_id]->Initialize();
+            if (res != NO_ERROR) {
+                ALOGE("%s: Unable to intialize back camera %d: %s (%d)",
+                        __FUNCTION__, camera_id, strerror(-res), res);
                 delete mEmulatedCameras[camera_id];
-                mEmulatedCameras--;
+                mEmulatedCameraNum--;
             }
         } else {
-            mEmulatedCameras--;
+            mEmulatedCameraNum--;
             ALOGE("%s: Unable to instantiate fake camera class", __FUNCTION__);
         }
     }
@@ -121,25 +127,31 @@
         switch (getFrontCameraHalVersion()) {
             case 1:
                 mEmulatedCameras[camera_id] =
-                        new EmulatedFakeCamera(camera_id, false, &HAL_MODULE_INFO_SYM.common);
+                        new EmulatedFakeCamera(camera_id, false,
+                                &HAL_MODULE_INFO_SYM.common);
                 break;
             case 2:
                 mEmulatedCameras[camera_id] =
-                        new EmulatedFakeCamera2(camera_id, false, &HAL_MODULE_INFO_SYM.common);
+                        new EmulatedFakeCamera2(camera_id, false,
+                                &HAL_MODULE_INFO_SYM.common);
                 break;
             default:
-                ALOGE("%s: Unknown front camera hal version requested: %d", __FUNCTION__,
+                ALOGE("%s: Unknown front camera hal version requested: %d",
+                        __FUNCTION__,
                         getFrontCameraHalVersion());
         }
         if (mEmulatedCameras[camera_id] != NULL) {
             ALOGV("%s: Front camera device version is %d", __FUNCTION__,
                     getFrontCameraHalVersion());
-            if (mEmulatedCameras[camera_id]->Initialize() != NO_ERROR) {
+            res = mEmulatedCameras[camera_id]->Initialize();
+            if (res != NO_ERROR) {
+                ALOGE("%s: Unable to intialize front camera %d: %s (%d)",
+                        __FUNCTION__, camera_id, strerror(-res), res);
                 delete mEmulatedCameras[camera_id];
-                mEmulatedCameras--;
+                mEmulatedCameraNum--;
             }
         } else {
-            mEmulatedCameras--;
+            mEmulatedCameraNum--;
             ALOGE("%s: Unable to instantiate fake camera class", __FUNCTION__);
         }
     }
diff --git a/tools/emulator/system/camera/EmulatedFakeCamera2.cpp b/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
index aa62244..4e4ee54 100644
--- a/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
+++ b/tools/emulator/system/camera/EmulatedFakeCamera2.cpp
@@ -19,15 +19,69 @@
  * functionality of an advanced fake camera.
  */
 
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
 #define LOG_TAG "EmulatedCamera_FakeCamera2"
-#include <cutils/log.h>
-#include <cutils/properties.h>
+#include <utils/Log.h>
+
 #include "EmulatedFakeCamera2.h"
 #include "EmulatedCameraFactory.h"
+#include <ui/Rect.h>
+#include <ui/GraphicBufferMapper.h>
+#include "gralloc_cb.h"
 
 namespace android {
 
+const int64_t USEC = 1000LL;
+const int64_t MSEC = USEC * 1000LL;
+const int64_t SEC = MSEC * 1000LL;
+
+const uint32_t EmulatedFakeCamera2::kAvailableFormats[4] = {
+        HAL_PIXEL_FORMAT_RAW_SENSOR,
+        HAL_PIXEL_FORMAT_BLOB,
+        HAL_PIXEL_FORMAT_RGBA_8888,
+        //        HAL_PIXEL_FORMAT_YV12,
+        HAL_PIXEL_FORMAT_YCrCb_420_SP
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableRawSizes[2] = {
+    640, 480
+    //    Sensor::kResolution[0], Sensor::kResolution[1]
+};
+
+const uint64_t EmulatedFakeCamera2::kAvailableRawMinDurations[1] = {
+    Sensor::kFrameDurationRange[0]
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableProcessedSizesBack[4] = {
+    640, 480, 320, 240
+    //    Sensor::kResolution[0], Sensor::kResolution[1]
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableProcessedSizesFront[4] = {
+    320, 240, 160, 120
+    //    Sensor::kResolution[0], Sensor::kResolution[1]
+};
+
+const uint64_t EmulatedFakeCamera2::kAvailableProcessedMinDurations[1] = {
+    Sensor::kFrameDurationRange[0]
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableJpegSizesBack[2] = {
+    640, 480
+    //    Sensor::kResolution[0], Sensor::kResolution[1]
+};
+
+const uint32_t EmulatedFakeCamera2::kAvailableJpegSizesFront[2] = {
+    320, 240
+    //    Sensor::kResolution[0], Sensor::kResolution[1]
+};
+
+
+const uint64_t EmulatedFakeCamera2::kAvailableJpegMinDurations[1] = {
+    Sensor::kFrameDurationRange[0]
+};
+
+
 EmulatedFakeCamera2::EmulatedFakeCamera2(int cameraId,
         bool facingBack,
         struct hw_module_t* module)
@@ -38,17 +92,2614 @@
             facingBack ? "back" : "front");
 }
 
-EmulatedFakeCamera2::~EmulatedFakeCamera2()
-{
+EmulatedFakeCamera2::~EmulatedFakeCamera2() {
+    if (mCameraInfo != NULL) {
+        free_camera_metadata(mCameraInfo);
+    }
 }
 
 /****************************************************************************
  * Public API overrides
  ***************************************************************************/
 
-status_t EmulatedFakeCamera2::Initialize()
-{
+status_t EmulatedFakeCamera2::Initialize() {
+    status_t res;
+
+    set_camera_metadata_vendor_tag_ops(
+            static_cast<vendor_tag_query_ops_t*>(&mVendorTagOps));
+
+    res = constructStaticInfo(&mCameraInfo, true);
+    if (res != OK) {
+        ALOGE("%s: Unable to allocate static info: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        return res;
+    }
+    res = constructStaticInfo(&mCameraInfo, false);
+    if (res != OK) {
+        ALOGE("%s: Unable to fill in static info: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        return res;
+    }
+    if (res != OK) return res;
+
+    mNextStreamId = 1;
+    mNextReprocessStreamId = 1;
+    mRawStreamCount = 0;
+    mProcessedStreamCount = 0;
+    mJpegStreamCount = 0;
+    mReprocessStreamCount = 0;
+
     return NO_ERROR;
 }
 
+/****************************************************************************
+ * Camera module API overrides
+ ***************************************************************************/
+
+status_t EmulatedFakeCamera2::connectCamera(hw_device_t** device) {
+    status_t res;
+    ALOGV("%s", __FUNCTION__);
+
+    mConfigureThread = new ConfigureThread(this);
+    mReadoutThread = new ReadoutThread(this);
+    mControlThread = new ControlThread(this);
+    mSensor = new Sensor(this);
+    mJpegCompressor = new JpegCompressor(this);
+
+    mNextStreamId = 1;
+    mNextReprocessStreamId = 1;
+
+    res = mSensor->startUp();
+    if (res != NO_ERROR) return res;
+
+    res = mConfigureThread->run("EmulatedFakeCamera2::configureThread");
+    if (res != NO_ERROR) return res;
+
+    res = mReadoutThread->run("EmulatedFakeCamera2::readoutThread");
+    if (res != NO_ERROR) return res;
+
+    res = mControlThread->run("EmulatedFakeCamera2::controlThread");
+    if (res != NO_ERROR) return res;
+
+    return EmulatedCamera2::connectCamera(device);
+}
+
+status_t EmulatedFakeCamera2::closeCamera() {
+    Mutex::Autolock l(mMutex);
+
+    status_t res;
+    ALOGV("%s", __FUNCTION__);
+
+    res = mSensor->shutDown();
+    if (res != NO_ERROR) {
+        ALOGE("%s: Unable to shut down sensor: %d", __FUNCTION__, res);
+        return res;
+    }
+
+    mConfigureThread->requestExit();
+    mReadoutThread->requestExit();
+    mControlThread->requestExit();
+    mJpegCompressor->cancel();
+
+    mConfigureThread->join();
+    mReadoutThread->join();
+    mControlThread->join();
+
+    ALOGV("%s exit", __FUNCTION__);
+    return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::getCameraInfo(struct camera_info *info) {
+    info->facing = mFacingBack ? CAMERA_FACING_BACK : CAMERA_FACING_FRONT;
+    info->orientation = gEmulatedCameraFactory.getFakeCameraOrientation();
+    return EmulatedCamera2::getCameraInfo(info);
+}
+
+/****************************************************************************
+ * Camera device API overrides
+ ***************************************************************************/
+
+/** Request input queue */
+
+int EmulatedFakeCamera2::requestQueueNotify() {
+    ALOGV("Request queue notification received");
+
+    ALOG_ASSERT(mRequestQueueSrc != NULL,
+            "%s: Request queue src not set, but received queue notification!",
+            __FUNCTION__);
+    ALOG_ASSERT(mFrameQueueDst != NULL,
+            "%s: Request queue src not set, but received queue notification!",
+            __FUNCTION__);
+    ALOG_ASSERT(mStreams.size() != 0,
+            "%s: No streams allocated, but received queue notification!",
+            __FUNCTION__);
+    return mConfigureThread->newRequestAvailable();
+}
+
+int EmulatedFakeCamera2::getInProgressCount() {
+    Mutex::Autolock l(mMutex);
+
+    int requestCount = 0;
+    requestCount += mConfigureThread->getInProgressCount();
+    requestCount += mReadoutThread->getInProgressCount();
+    requestCount += mJpegCompressor->isBusy() ? 1 : 0;
+
+    return requestCount;
+}
+
+int EmulatedFakeCamera2::constructDefaultRequest(
+        int request_template,
+        camera_metadata_t **request) {
+
+    if (request == NULL) return BAD_VALUE;
+    if (request_template < 0 || request_template >= CAMERA2_TEMPLATE_COUNT) {
+        return BAD_VALUE;
+    }
+    status_t res;
+    // Pass 1, calculate size and allocate
+    res = constructDefaultRequest(request_template,
+            request,
+            true);
+    if (res != OK) {
+        return res;
+    }
+    // Pass 2, build request
+    res = constructDefaultRequest(request_template,
+            request,
+            false);
+    if (res != OK) {
+        ALOGE("Unable to populate new request for template %d",
+                request_template);
+    }
+
+    return res;
+}
+
+int EmulatedFakeCamera2::allocateStream(
+        uint32_t width,
+        uint32_t height,
+        int format,
+        const camera2_stream_ops_t *stream_ops,
+        uint32_t *stream_id,
+        uint32_t *format_actual,
+        uint32_t *usage,
+        uint32_t *max_buffers) {
+    Mutex::Autolock l(mMutex);
+
+    // Temporary shim until FORMAT_ZSL is removed
+    if (format == CAMERA2_HAL_PIXEL_FORMAT_ZSL) {
+        format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+    }
+
+    if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+        unsigned int numFormats = sizeof(kAvailableFormats) / sizeof(uint32_t);
+        unsigned int formatIdx = 0;
+        unsigned int sizeOffsetIdx = 0;
+        for (; formatIdx < numFormats; formatIdx++) {
+            if (format == (int)kAvailableFormats[formatIdx]) break;
+        }
+        if (formatIdx == numFormats) {
+            ALOGE("%s: Format 0x%x is not supported", __FUNCTION__, format);
+            return BAD_VALUE;
+        }
+    }
+
+    const uint32_t *availableSizes;
+    size_t availableSizeCount;
+    switch (format) {
+        case HAL_PIXEL_FORMAT_RAW_SENSOR:
+            availableSizes = kAvailableRawSizes;
+            availableSizeCount = sizeof(kAvailableRawSizes)/sizeof(uint32_t);
+            break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            availableSizes = mFacingBack ?
+                    kAvailableJpegSizesBack : kAvailableJpegSizesFront;
+            availableSizeCount = mFacingBack ?
+                    sizeof(kAvailableJpegSizesBack)/sizeof(uint32_t) :
+                    sizeof(kAvailableJpegSizesFront)/sizeof(uint32_t);
+            break;
+        case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+        case HAL_PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_YV12:
+        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+            availableSizes = mFacingBack ?
+                    kAvailableProcessedSizesBack : kAvailableProcessedSizesFront;
+            availableSizeCount = mFacingBack ?
+                    sizeof(kAvailableProcessedSizesBack)/sizeof(uint32_t) :
+                    sizeof(kAvailableProcessedSizesFront)/sizeof(uint32_t);
+            break;
+        default:
+            ALOGE("%s: Unknown format 0x%x", __FUNCTION__, format);
+            return BAD_VALUE;
+    }
+
+    unsigned int resIdx = 0;
+    for (; resIdx < availableSizeCount; resIdx++) {
+        if (availableSizes[resIdx * 2] == width &&
+                availableSizes[resIdx * 2 + 1] == height) break;
+    }
+    if (resIdx == availableSizeCount) {
+        ALOGE("%s: Format 0x%x does not support resolution %d, %d", __FUNCTION__,
+                format, width, height);
+        return BAD_VALUE;
+    }
+
+    switch (format) {
+        case HAL_PIXEL_FORMAT_RAW_SENSOR:
+            if (mRawStreamCount >= kMaxRawStreamCount) {
+                ALOGE("%s: Cannot allocate another raw stream (%d already allocated)",
+                        __FUNCTION__, mRawStreamCount);
+                return INVALID_OPERATION;
+            }
+            mRawStreamCount++;
+            break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            if (mJpegStreamCount >= kMaxJpegStreamCount) {
+                ALOGE("%s: Cannot allocate another JPEG stream (%d already allocated)",
+                        __FUNCTION__, mJpegStreamCount);
+                return INVALID_OPERATION;
+            }
+            mJpegStreamCount++;
+            break;
+        default:
+            if (mProcessedStreamCount >= kMaxProcessedStreamCount) {
+                ALOGE("%s: Cannot allocate another processed stream (%d already allocated)",
+                        __FUNCTION__, mProcessedStreamCount);
+                return INVALID_OPERATION;
+            }
+            mProcessedStreamCount++;
+    }
+
+    Stream newStream;
+    newStream.ops = stream_ops;
+    newStream.width = width;
+    newStream.height = height;
+    newStream.format = format;
+    // TODO: Query stride from gralloc
+    newStream.stride = width;
+
+    mStreams.add(mNextStreamId, newStream);
+
+    *stream_id = mNextStreamId;
+    if (format_actual) *format_actual = format;
+    *usage = GRALLOC_USAGE_HW_CAMERA_WRITE;
+    *max_buffers = kMaxBufferCount;
+
+    ALOGV("Stream allocated: %d, %d x %d, 0x%x. U: %x, B: %d",
+            *stream_id, width, height, format, *usage, *max_buffers);
+
+    mNextStreamId++;
+    return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::registerStreamBuffers(
+            uint32_t stream_id,
+            int num_buffers,
+            buffer_handle_t *buffers) {
+    Mutex::Autolock l(mMutex);
+
+    ALOGV("%s: Stream %d registering %d buffers", __FUNCTION__,
+            stream_id, num_buffers);
+    // Need to find out what the final concrete pixel format for our stream is
+    // Assumes that all buffers have the same format.
+    if (num_buffers < 1) {
+        ALOGE("%s: Stream %d only has %d buffers!",
+                __FUNCTION__, stream_id, num_buffers);
+        return BAD_VALUE;
+    }
+    const cb_handle_t *streamBuffer =
+            reinterpret_cast<const cb_handle_t*>(buffers[0]);
+
+    int finalFormat = streamBuffer->format;
+
+    if (finalFormat == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+        ALOGE("%s: Stream %d: Bad final pixel format "
+                "HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED; "
+                "concrete pixel format required!", __FUNCTION__, stream_id);
+        return BAD_VALUE;
+    }
+
+    ssize_t streamIndex = mStreams.indexOfKey(stream_id);
+    if (streamIndex < 0) {
+        ALOGE("%s: Unknown stream id %d!", __FUNCTION__, stream_id);
+        return BAD_VALUE;
+    }
+
+    Stream &stream = mStreams.editValueAt(streamIndex);
+
+    ALOGV("%s: Stream %d format set to %x, previously %x",
+            __FUNCTION__, stream_id, finalFormat, stream.format);
+
+    stream.format = finalFormat;
+
+    return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::releaseStream(uint32_t stream_id) {
+    Mutex::Autolock l(mMutex);
+
+    ssize_t streamIndex = mStreams.indexOfKey(stream_id);
+    if (streamIndex < 0) {
+        ALOGE("%s: Unknown stream id %d!", __FUNCTION__, stream_id);
+        return BAD_VALUE;
+    }
+
+    if (isStreamInUse(stream_id)) {
+        ALOGE("%s: Cannot release stream %d; in use!", __FUNCTION__,
+                stream_id);
+        return BAD_VALUE;
+    }
+
+    switch(mStreams.valueAt(streamIndex).format) {
+        case HAL_PIXEL_FORMAT_RAW_SENSOR:
+            mRawStreamCount--;
+            break;
+        case HAL_PIXEL_FORMAT_BLOB:
+            mJpegStreamCount--;
+            break;
+        default:
+            mProcessedStreamCount--;
+            break;
+    }
+
+    mStreams.removeItemsAt(streamIndex);
+
+    return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::allocateReprocessStreamFromStream(
+        uint32_t output_stream_id,
+        const camera2_stream_in_ops_t *stream_ops,
+        uint32_t *stream_id) {
+    Mutex::Autolock l(mMutex);
+
+    ssize_t baseStreamIndex = mStreams.indexOfKey(output_stream_id);
+    if (baseStreamIndex < 0) {
+        ALOGE("%s: Unknown output stream id %d!", __FUNCTION__, output_stream_id);
+        return BAD_VALUE;
+    }
+
+    const Stream &baseStream = mStreams[baseStreamIndex];
+
+    // We'll reprocess anything we produced
+
+    if (mReprocessStreamCount >= kMaxReprocessStreamCount) {
+        ALOGE("%s: Cannot allocate another reprocess stream (%d already allocated)",
+                __FUNCTION__, mReprocessStreamCount);
+        return INVALID_OPERATION;
+    }
+    mReprocessStreamCount++;
+
+    ReprocessStream newStream;
+    newStream.ops = stream_ops;
+    newStream.width = baseStream.width;
+    newStream.height = baseStream.height;
+    newStream.format = baseStream.format;
+    newStream.stride = baseStream.stride;
+    newStream.sourceStreamId = output_stream_id;
+
+    *stream_id = mNextReprocessStreamId;
+    mReprocessStreams.add(mNextReprocessStreamId, newStream);
+
+    ALOGV("Reprocess stream allocated: %d: %d, %d, 0x%x. Parent stream: %d",
+            *stream_id, newStream.width, newStream.height, newStream.format,
+            output_stream_id);
+
+    mNextReprocessStreamId++;
+    return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::releaseReprocessStream(uint32_t stream_id) {
+    Mutex::Autolock l(mMutex);
+
+    ssize_t streamIndex = mReprocessStreams.indexOfKey(stream_id);
+    if (streamIndex < 0) {
+        ALOGE("%s: Unknown reprocess stream id %d!", __FUNCTION__, stream_id);
+        return BAD_VALUE;
+    }
+
+    if (isReprocessStreamInUse(stream_id)) {
+        ALOGE("%s: Cannot release reprocessing stream %d; in use!", __FUNCTION__,
+                stream_id);
+        return BAD_VALUE;
+    }
+
+    mReprocessStreamCount--;
+    mReprocessStreams.removeItemsAt(streamIndex);
+
+    return NO_ERROR;
+}
+
+int EmulatedFakeCamera2::triggerAction(uint32_t trigger_id,
+        int32_t ext1,
+        int32_t ext2) {
+    Mutex::Autolock l(mMutex);
+    return mControlThread->triggerAction(trigger_id,
+            ext1, ext2);
+}
+
+/** Custom tag definitions */
+
+// Emulator camera metadata sections
+enum {
+    EMULATOR_SCENE = VENDOR_SECTION,
+    END_EMULATOR_SECTIONS
+};
+
+enum {
+    EMULATOR_SCENE_START = EMULATOR_SCENE << 16,
+};
+
+// Emulator camera metadata tags
+enum {
+    // Hour of day to use for lighting calculations (0-23). Default: 12
+    EMULATOR_SCENE_HOUROFDAY = EMULATOR_SCENE_START,
+    EMULATOR_SCENE_END
+};
+
+unsigned int emulator_metadata_section_bounds[END_EMULATOR_SECTIONS -
+        VENDOR_SECTION][2] = {
+    { EMULATOR_SCENE_START, EMULATOR_SCENE_END }
+};
+
+const char *emulator_metadata_section_names[END_EMULATOR_SECTIONS -
+        VENDOR_SECTION] = {
+    "com.android.emulator.scene"
+};
+
+typedef struct emulator_tag_info {
+    const char *tag_name;
+    uint8_t     tag_type;
+} emulator_tag_info_t;
+
+emulator_tag_info_t emulator_scene[EMULATOR_SCENE_END - EMULATOR_SCENE_START] = {
+    { "hourOfDay", TYPE_INT32 }
+};
+
+emulator_tag_info_t *tag_info[END_EMULATOR_SECTIONS -
+        VENDOR_SECTION] = {
+    emulator_scene
+};
+
+const char* EmulatedFakeCamera2::getVendorSectionName(uint32_t tag) {
+    ALOGV("%s", __FUNCTION__);
+    uint32_t section = tag >> 16;
+    if (section < VENDOR_SECTION || section > END_EMULATOR_SECTIONS) return NULL;
+    return emulator_metadata_section_names[section - VENDOR_SECTION];
+}
+
+const char* EmulatedFakeCamera2::getVendorTagName(uint32_t tag) {
+    ALOGV("%s", __FUNCTION__);
+    uint32_t section = tag >> 16;
+    if (section < VENDOR_SECTION || section > END_EMULATOR_SECTIONS) return NULL;
+    uint32_t section_index = section - VENDOR_SECTION;
+    if (tag >= emulator_metadata_section_bounds[section_index][1]) {
+        return NULL;
+    }
+    uint32_t tag_index = tag & 0xFFFF;
+    return tag_info[section_index][tag_index].tag_name;
+}
+
+int EmulatedFakeCamera2::getVendorTagType(uint32_t tag) {
+    ALOGV("%s", __FUNCTION__);
+    uint32_t section = tag >> 16;
+    if (section < VENDOR_SECTION || section > END_EMULATOR_SECTIONS) return -1;
+    uint32_t section_index = section - VENDOR_SECTION;
+    if (tag >= emulator_metadata_section_bounds[section_index][1]) {
+        return -1;
+    }
+    uint32_t tag_index = tag & 0xFFFF;
+    return tag_info[section_index][tag_index].tag_type;
+}
+
+/** Shutdown and debug methods */
+
+int EmulatedFakeCamera2::dump(int fd) {
+    String8 result;
+
+    result.appendFormat("    Camera HAL device: EmulatedFakeCamera2\n");
+    result.appendFormat("      Streams:\n");
+    for (size_t i = 0; i < mStreams.size(); i++) {
+        int id = mStreams.keyAt(i);
+        const Stream& s = mStreams.valueAt(i);
+        result.appendFormat(
+            "         Stream %d: %d x %d, format 0x%x, stride %d\n",
+            id, s.width, s.height, s.format, s.stride);
+    }
+
+    write(fd, result.string(), result.size());
+
+    return NO_ERROR;
+}
+
+void EmulatedFakeCamera2::signalError() {
+    // TODO: Let parent know so we can shut down cleanly
+    ALOGE("Worker thread is signaling a serious error");
+}
+
+/** Pipeline control worker thread methods */
+
+EmulatedFakeCamera2::ConfigureThread::ConfigureThread(EmulatedFakeCamera2 *parent):
+        Thread(false),
+        mParent(parent),
+        mRequestCount(0),
+        mNextBuffers(NULL) {
+    mRunning = false;
+}
+
+EmulatedFakeCamera2::ConfigureThread::~ConfigureThread() {
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::readyToRun() {
+    Mutex::Autolock lock(mInputMutex);
+
+    ALOGV("Starting up ConfigureThread");
+    mRequest = NULL;
+    mActive  = false;
+    mRunning = true;
+
+    mInputSignal.signal();
+    return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::waitUntilRunning() {
+    Mutex::Autolock lock(mInputMutex);
+    if (!mRunning) {
+        ALOGV("Waiting for configure thread to start");
+        mInputSignal.wait(mInputMutex);
+    }
+    return OK;
+}
+
+status_t EmulatedFakeCamera2::ConfigureThread::newRequestAvailable() {
+    waitUntilRunning();
+
+    Mutex::Autolock lock(mInputMutex);
+
+    mActive = true;
+    mInputSignal.signal();
+
+    return OK;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::isStreamInUse(uint32_t id) {
+    Mutex::Autolock lock(mInternalsMutex);
+
+    if (mNextBuffers == NULL) return false;
+    for (size_t i=0; i < mNextBuffers->size(); i++) {
+        if ((*mNextBuffers)[i].streamId == (int)id) return true;
+    }
+    return false;
+}
+
+int EmulatedFakeCamera2::ConfigureThread::getInProgressCount() {
+    Mutex::Autolock lock(mInputMutex);
+    return mRequestCount;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::threadLoop() {
+    status_t res;
+
+    // Check if we're currently processing or just waiting
+    {
+        Mutex::Autolock lock(mInputMutex);
+        if (!mActive) {
+            // Inactive, keep waiting until we've been signaled
+            status_t res;
+            res = mInputSignal.waitRelative(mInputMutex, kWaitPerLoop);
+            if (res != NO_ERROR && res != TIMED_OUT) {
+                ALOGE("%s: Error waiting for input requests: %d",
+                        __FUNCTION__, res);
+                return false;
+            }
+            if (!mActive) return true;
+            ALOGV("New request available");
+        }
+        // Active
+    }
+    if (mRequest == NULL) {
+        Mutex::Autolock il(mInternalsMutex);
+
+        ALOGV("Configure: Getting next request");
+        res = mParent->mRequestQueueSrc->dequeue_request(
+            mParent->mRequestQueueSrc,
+            &mRequest);
+        if (res != NO_ERROR) {
+            ALOGE("%s: Error dequeuing next request: %d", __FUNCTION__, res);
+            mParent->signalError();
+            return false;
+        }
+        if (mRequest == NULL) {
+            ALOGV("Configure: Request queue empty, going inactive");
+            // No requests available, go into inactive mode
+            Mutex::Autolock lock(mInputMutex);
+            mActive = false;
+            return true;
+        } else {
+            Mutex::Autolock lock(mInputMutex);
+            mRequestCount++;
+        }
+
+        camera_metadata_entry_t type;
+        res = find_camera_metadata_entry(mRequest,
+                ANDROID_REQUEST_TYPE,
+                &type);
+        if (res != NO_ERROR) {
+            ALOGE("%s: error reading request type", __FUNCTION__);
+            mParent->signalError();
+            return false;
+        }
+        bool success = false;;
+        switch (type.data.u8[0]) {
+            case ANDROID_REQUEST_TYPE_CAPTURE:
+                success = setupCapture();
+                break;
+            case ANDROID_REQUEST_TYPE_REPROCESS:
+                success = setupReprocess();
+                break;
+            default:
+                ALOGE("%s: Unexpected request type %d",
+                        __FUNCTION__, type.data.u8[0]);
+                mParent->signalError();
+                break;
+        }
+        if (!success) return false;
+
+    }
+
+    if (mWaitingForReadout) {
+        bool readoutDone;
+        readoutDone = mParent->mReadoutThread->waitForReady(kWaitPerLoop);
+        if (!readoutDone) return true;
+
+        if (mNextNeedsJpeg) {
+            ALOGV("Configure: Waiting for JPEG compressor");
+        } else {
+            ALOGV("Configure: Waiting for sensor");
+        }
+        mWaitingForReadout = false;
+    }
+
+    if (mNextNeedsJpeg) {
+        bool jpegDone;
+        jpegDone = mParent->mJpegCompressor->waitForDone(kWaitPerLoop);
+        if (!jpegDone) return true;
+
+        ALOGV("Configure: Waiting for sensor");
+        mNextNeedsJpeg = false;
+    }
+
+    if (mNextIsCapture) {
+        return configureNextCapture();
+    } else {
+        return configureNextReprocess();
+    }
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::setupCapture() {
+    status_t res;
+
+    mNextIsCapture = true;
+    // Get necessary parameters for sensor config
+    mParent->mControlThread->processRequest(mRequest);
+
+    camera_metadata_entry_t streams;
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_OUTPUT_STREAMS,
+            &streams);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading output stream tag", __FUNCTION__);
+        mParent->signalError();
+        return false;
+    }
+
+    mNextBuffers = new Buffers;
+    mNextNeedsJpeg = false;
+    ALOGV("Configure: Setting up buffers for capture");
+    for (size_t i = 0; i < streams.count; i++) {
+        int streamId = streams.data.u8[i];
+        const Stream &s = mParent->getStreamInfo(streamId);
+        if (s.format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) {
+            ALOGE("%s: Stream %d does not have a concrete pixel format, but "
+                    "is included in a request!", __FUNCTION__, streamId);
+            mParent->signalError();
+            return false;
+        }
+        StreamBuffer b;
+        b.streamId = streams.data.u8[i];
+        b.width  = s.width;
+        b.height = s.height;
+        b.format = s.format;
+        b.stride = s.stride;
+        mNextBuffers->push_back(b);
+        ALOGV("Configure:    Buffer %d: Stream %d, %d x %d, format 0x%x, "
+                "stride %d",
+                i, b.streamId, b.width, b.height, b.format, b.stride);
+        if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+            mNextNeedsJpeg = true;
+        }
+    }
+
+    camera_metadata_entry_t e;
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_FRAME_COUNT,
+            &e);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading frame count tag: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        mParent->signalError();
+        return false;
+    }
+    mNextFrameNumber = *e.data.i32;
+
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_SENSOR_EXPOSURE_TIME,
+            &e);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading exposure time tag: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        mParent->signalError();
+        return false;
+    }
+    mNextExposureTime = *e.data.i64;
+
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_SENSOR_FRAME_DURATION,
+            &e);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading frame duration tag", __FUNCTION__);
+        mParent->signalError();
+        return false;
+    }
+    mNextFrameDuration = *e.data.i64;
+
+    if (mNextFrameDuration <
+            mNextExposureTime + Sensor::kMinVerticalBlank) {
+        mNextFrameDuration = mNextExposureTime + Sensor::kMinVerticalBlank;
+    }
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_SENSOR_SENSITIVITY,
+            &e);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading sensitivity tag", __FUNCTION__);
+        mParent->signalError();
+        return false;
+    }
+    mNextSensitivity = *e.data.i32;
+
+    res = find_camera_metadata_entry(mRequest,
+            EMULATOR_SCENE_HOUROFDAY,
+            &e);
+    if (res == NO_ERROR) {
+        ALOGV("Setting hour: %d", *e.data.i32);
+        mParent->mSensor->getScene().setHour(*e.data.i32);
+    }
+
+    // Start waiting on readout thread
+    mWaitingForReadout = true;
+    ALOGV("Configure: Waiting for readout thread");
+
+    return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::configureNextCapture() {
+    bool vsync = mParent->mSensor->waitForVSync(kWaitPerLoop);
+    if (!vsync) return true;
+
+    Mutex::Autolock il(mInternalsMutex);
+    ALOGV("Configure: Configuring sensor for capture %d", mNextFrameNumber);
+    mParent->mSensor->setExposureTime(mNextExposureTime);
+    mParent->mSensor->setFrameDuration(mNextFrameDuration);
+    mParent->mSensor->setSensitivity(mNextSensitivity);
+
+    getBuffers();
+
+    ALOGV("Configure: Done configure for capture %d", mNextFrameNumber);
+    mParent->mReadoutThread->setNextOperation(true, mRequest, mNextBuffers);
+    mParent->mSensor->setDestinationBuffers(mNextBuffers);
+
+    mRequest = NULL;
+    mNextBuffers = NULL;
+
+    Mutex::Autolock lock(mInputMutex);
+    mRequestCount--;
+
+    return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::setupReprocess() {
+    status_t res;
+
+    mNextNeedsJpeg = true;
+    mNextIsCapture = false;
+
+    camera_metadata_entry_t reprocessStreams;
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_INPUT_STREAMS,
+            &reprocessStreams);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading output stream tag", __FUNCTION__);
+        mParent->signalError();
+        return false;
+    }
+
+    mNextBuffers = new Buffers;
+
+    ALOGV("Configure: Setting up input buffers for reprocess");
+    for (size_t i = 0; i < reprocessStreams.count; i++) {
+        int streamId = reprocessStreams.data.u8[i];
+        const ReprocessStream &s = mParent->getReprocessStreamInfo(streamId);
+        if (s.format != HAL_PIXEL_FORMAT_RGB_888) {
+            ALOGE("%s: Only ZSL reprocessing supported!",
+                    __FUNCTION__);
+            mParent->signalError();
+            return false;
+        }
+        StreamBuffer b;
+        b.streamId = -streamId;
+        b.width = s.width;
+        b.height = s.height;
+        b.format = s.format;
+        b.stride = s.stride;
+        mNextBuffers->push_back(b);
+    }
+
+    camera_metadata_entry_t streams;
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_OUTPUT_STREAMS,
+            &streams);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading output stream tag", __FUNCTION__);
+        mParent->signalError();
+        return false;
+    }
+
+    ALOGV("Configure: Setting up output buffers for reprocess");
+    for (size_t i = 0; i < streams.count; i++) {
+        int streamId = streams.data.u8[i];
+        const Stream &s = mParent->getStreamInfo(streamId);
+        if (s.format != HAL_PIXEL_FORMAT_BLOB) {
+            // TODO: Support reprocess to YUV
+            ALOGE("%s: Non-JPEG output stream %d for reprocess not supported",
+                    __FUNCTION__, streamId);
+            mParent->signalError();
+            return false;
+        }
+        StreamBuffer b;
+        b.streamId = streams.data.u8[i];
+        b.width  = s.width;
+        b.height = s.height;
+        b.format = s.format;
+        b.stride = s.stride;
+        mNextBuffers->push_back(b);
+        ALOGV("Configure:    Buffer %d: Stream %d, %d x %d, format 0x%x, "
+                "stride %d",
+                i, b.streamId, b.width, b.height, b.format, b.stride);
+    }
+
+    camera_metadata_entry_t e;
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_FRAME_COUNT,
+            &e);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading frame count tag: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        mParent->signalError();
+        return false;
+    }
+    mNextFrameNumber = *e.data.i32;
+
+    return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::configureNextReprocess() {
+    Mutex::Autolock il(mInternalsMutex);
+
+    getBuffers();
+
+    ALOGV("Configure: Done configure for reprocess %d", mNextFrameNumber);
+    mParent->mReadoutThread->setNextOperation(false, mRequest, mNextBuffers);
+
+    mRequest = NULL;
+    mNextBuffers = NULL;
+
+    Mutex::Autolock lock(mInputMutex);
+    mRequestCount--;
+
+    return true;
+}
+
+bool EmulatedFakeCamera2::ConfigureThread::getBuffers() {
+    status_t res;
+    /** Get buffers to fill for this frame */
+    for (size_t i = 0; i < mNextBuffers->size(); i++) {
+        StreamBuffer &b = mNextBuffers->editItemAt(i);
+
+        if (b.streamId > 0) {
+            Stream s = mParent->getStreamInfo(b.streamId);
+            ALOGV("Configure: Dequeing buffer from stream %d", b.streamId);
+            res = s.ops->dequeue_buffer(s.ops, &(b.buffer) );
+            if (res != NO_ERROR || b.buffer == NULL) {
+                ALOGE("%s: Unable to dequeue buffer from stream %d: %s (%d)",
+                        __FUNCTION__, b.streamId, strerror(-res), res);
+                mParent->signalError();
+                return false;
+            }
+
+            /* Lock the buffer from the perspective of the graphics mapper */
+            const Rect rect(s.width, s.height);
+
+            res = GraphicBufferMapper::get().lock(*(b.buffer),
+                    GRALLOC_USAGE_HW_CAMERA_WRITE,
+                    rect, (void**)&(b.img) );
+
+            if (res != NO_ERROR) {
+                ALOGE("%s: grbuffer_mapper.lock failure: %s (%d)",
+                        __FUNCTION__, strerror(-res), res);
+                s.ops->cancel_buffer(s.ops,
+                        b.buffer);
+                mParent->signalError();
+                return false;
+            }
+        } else {
+            ReprocessStream s = mParent->getReprocessStreamInfo(-b.streamId);
+            ALOGV("Configure: Acquiring buffer from reprocess stream %d",
+                    -b.streamId);
+            res = s.ops->acquire_buffer(s.ops, &(b.buffer) );
+            if (res != NO_ERROR || b.buffer == NULL) {
+                ALOGE("%s: Unable to acquire buffer from reprocess stream %d: "
+                        "%s (%d)", __FUNCTION__, -b.streamId,
+                        strerror(-res), res);
+                mParent->signalError();
+                return false;
+            }
+
+            /* Lock the buffer from the perspective of the graphics mapper */
+            const Rect rect(s.width, s.height);
+
+            res = GraphicBufferMapper::get().lock(*(b.buffer),
+                    GRALLOC_USAGE_HW_CAMERA_READ,
+                    rect, (void**)&(b.img) );
+            if (res != NO_ERROR) {
+                ALOGE("%s: grbuffer_mapper.lock failure: %s (%d)",
+                        __FUNCTION__, strerror(-res), res);
+                s.ops->release_buffer(s.ops,
+                        b.buffer);
+                mParent->signalError();
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+EmulatedFakeCamera2::ReadoutThread::ReadoutThread(EmulatedFakeCamera2 *parent):
+        Thread(false),
+        mParent(parent),
+        mRunning(false),
+        mActive(false),
+        mRequestCount(0),
+        mRequest(NULL),
+        mBuffers(NULL) {
+    mInFlightQueue = new InFlightQueue[kInFlightQueueSize];
+    mInFlightHead = 0;
+    mInFlightTail = 0;
+}
+
+EmulatedFakeCamera2::ReadoutThread::~ReadoutThread() {
+    delete mInFlightQueue;
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::readyToRun() {
+    Mutex::Autolock lock(mInputMutex);
+    ALOGV("Starting up ReadoutThread");
+    mRunning = true;
+    mInputSignal.signal();
+    return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::waitUntilRunning() {
+    Mutex::Autolock lock(mInputMutex);
+    if (!mRunning) {
+        ALOGV("Waiting for readout thread to start");
+        mInputSignal.wait(mInputMutex);
+    }
+    return OK;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::waitForReady(nsecs_t timeout) {
+    status_t res;
+    Mutex::Autolock lock(mInputMutex);
+    while (!readyForNextCapture()) {
+        res = mReadySignal.waitRelative(mInputMutex, timeout);
+        if (res == TIMED_OUT) return false;
+        if (res != OK) {
+            ALOGE("%s: Error waiting for ready: %s (%d)", __FUNCTION__,
+                    strerror(-res), res);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::readyForNextCapture() {
+    return (mInFlightTail + 1) % kInFlightQueueSize != mInFlightHead;
+}
+
+void EmulatedFakeCamera2::ReadoutThread::setNextOperation(
+        bool isCapture,
+        camera_metadata_t *request,
+        Buffers *buffers) {
+    Mutex::Autolock lock(mInputMutex);
+    if ( !readyForNextCapture() ) {
+        ALOGE("In flight queue full, dropping captures");
+        mParent->signalError();
+        return;
+    }
+    mInFlightQueue[mInFlightTail].isCapture = isCapture;
+    mInFlightQueue[mInFlightTail].request = request;
+    mInFlightQueue[mInFlightTail].buffers = buffers;
+    mInFlightTail = (mInFlightTail + 1) % kInFlightQueueSize;
+    mRequestCount++;
+
+    if (!mActive) {
+        mActive = true;
+        mInputSignal.signal();
+    }
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::isStreamInUse(uint32_t id) {
+    Mutex::Autolock lock(mInputMutex);
+
+    size_t i = mInFlightHead;
+    while (i != mInFlightTail) {
+        for (size_t j = 0; j < mInFlightQueue[i].buffers->size(); j++) {
+            if ( (*(mInFlightQueue[i].buffers))[j].streamId == (int)id )
+                return true;
+        }
+        i = (i + 1) % kInFlightQueueSize;
+    }
+
+    Mutex::Autolock iLock(mInternalsMutex);
+
+    if (mBuffers != NULL) {
+        for (i = 0; i < mBuffers->size(); i++) {
+            if ( (*mBuffers)[i].streamId == (int)id) return true;
+        }
+    }
+
+    return false;
+}
+
+int EmulatedFakeCamera2::ReadoutThread::getInProgressCount() {
+    Mutex::Autolock lock(mInputMutex);
+
+    return mRequestCount;
+}
+
+bool EmulatedFakeCamera2::ReadoutThread::threadLoop() {
+    static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
+    status_t res;
+    int32_t frameNumber;
+
+    // Check if we're currently processing or just waiting
+    {
+        Mutex::Autolock lock(mInputMutex);
+        if (!mActive) {
+            // Inactive, keep waiting until we've been signaled
+            res = mInputSignal.waitRelative(mInputMutex, kWaitPerLoop);
+            if (res != NO_ERROR && res != TIMED_OUT) {
+                ALOGE("%s: Error waiting for capture requests: %d",
+                        __FUNCTION__, res);
+                mParent->signalError();
+                return false;
+            }
+            if (!mActive) return true;
+        }
+        // Active, see if we need a new request
+        if (mRequest == NULL) {
+            if (mInFlightHead == mInFlightTail) {
+                // Go inactive
+                ALOGV("Waiting for sensor data");
+                mActive = false;
+                return true;
+            } else {
+                Mutex::Autolock iLock(mInternalsMutex);
+                mReadySignal.signal();
+                mIsCapture = mInFlightQueue[mInFlightHead].isCapture;
+                mRequest = mInFlightQueue[mInFlightHead].request;
+                mBuffers  = mInFlightQueue[mInFlightHead].buffers;
+                mInFlightQueue[mInFlightHead].request = NULL;
+                mInFlightQueue[mInFlightHead].buffers = NULL;
+                mInFlightHead = (mInFlightHead + 1) % kInFlightQueueSize;
+                ALOGV("Ready to read out request %p, %d buffers",
+                        mRequest, mBuffers->size());
+            }
+        }
+    }
+
+    // Active with request, wait on sensor to complete
+
+    nsecs_t captureTime;
+
+    if (mIsCapture) {
+        bool gotFrame;
+        gotFrame = mParent->mSensor->waitForNewFrame(kWaitPerLoop,
+                &captureTime);
+
+        if (!gotFrame) return true;
+    }
+
+    Mutex::Autolock iLock(mInternalsMutex);
+
+    camera_metadata_entry_t entry;
+    if (!mIsCapture) {
+        res = find_camera_metadata_entry(mRequest,
+                ANDROID_SENSOR_TIMESTAMP,
+            &entry);
+        if (res != NO_ERROR) {
+            ALOGE("%s: error reading reprocessing timestamp: %s (%d)",
+                    __FUNCTION__, strerror(-res), res);
+            mParent->signalError();
+            return false;
+        }
+        captureTime = entry.data.i64[0];
+    }
+
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_FRAME_COUNT,
+            &entry);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading frame count tag: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        mParent->signalError();
+        return false;
+    }
+    frameNumber = *entry.data.i32;
+
+    res = find_camera_metadata_entry(mRequest,
+            ANDROID_REQUEST_METADATA_MODE,
+            &entry);
+    if (res != NO_ERROR) {
+        ALOGE("%s: error reading metadata mode tag: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        mParent->signalError();
+        return false;
+    }
+
+    // Got sensor data and request, construct frame and send it out
+    ALOGV("Readout: Constructing metadata and frames for request %d",
+            frameNumber);
+
+    if (*entry.data.u8 == ANDROID_REQUEST_METADATA_FULL) {
+        ALOGV("Readout: Metadata requested, constructing");
+
+        camera_metadata_t *frame = NULL;
+
+        size_t frame_entries = get_camera_metadata_entry_count(mRequest);
+        size_t frame_data    = get_camera_metadata_data_count(mRequest);
+
+        // TODO: Dynamically calculate based on enabled statistics, etc
+        frame_entries += 10;
+        frame_data += 100;
+
+        res = mParent->mFrameQueueDst->dequeue_frame(mParent->mFrameQueueDst,
+                frame_entries, frame_data, &frame);
+
+        if (res != NO_ERROR || frame == NULL) {
+            ALOGE("%s: Unable to dequeue frame metadata buffer", __FUNCTION__);
+            mParent->signalError();
+            return false;
+        }
+
+        res = append_camera_metadata(frame, mRequest);
+        if (res != NO_ERROR) {
+            ALOGE("Unable to append request metadata");
+        }
+
+        if (mIsCapture) {
+            add_camera_metadata_entry(frame,
+                    ANDROID_SENSOR_TIMESTAMP,
+                    &captureTime,
+                    1);
+
+            int32_t hourOfDay = (int32_t)mParent->mSensor->getScene().getHour();
+            camera_metadata_entry_t requestedHour;
+            res = find_camera_metadata_entry(frame,
+                    EMULATOR_SCENE_HOUROFDAY,
+                    &requestedHour);
+            if (res == NAME_NOT_FOUND) {
+                res = add_camera_metadata_entry(frame,
+                        EMULATOR_SCENE_HOUROFDAY,
+                        &hourOfDay, 1);
+                if (res != NO_ERROR) {
+                    ALOGE("Unable to add vendor tag");
+                }
+            } else if (res == OK) {
+                *requestedHour.data.i32 = hourOfDay;
+            } else {
+                ALOGE("%s: Error looking up vendor tag", __FUNCTION__);
+            }
+
+            collectStatisticsMetadata(frame);
+            // TODO: Collect all final values used from sensor in addition to timestamp
+        }
+
+        ALOGV("Readout: Enqueue frame %d", frameNumber);
+        mParent->mFrameQueueDst->enqueue_frame(mParent->mFrameQueueDst,
+                frame);
+    }
+    ALOGV("Readout: Free request");
+    res = mParent->mRequestQueueSrc->free_request(mParent->mRequestQueueSrc, mRequest);
+    if (res != NO_ERROR) {
+        ALOGE("%s: Unable to return request buffer to queue: %d",
+                __FUNCTION__, res);
+        mParent->signalError();
+        return false;
+    }
+    mRequest = NULL;
+
+    int compressedBufferIndex = -1;
+    ALOGV("Readout: Processing %d buffers", mBuffers->size());
+    for (size_t i = 0; i < mBuffers->size(); i++) {
+        const StreamBuffer &b = (*mBuffers)[i];
+        ALOGV("Readout:    Buffer %d: Stream %d, %d x %d, format 0x%x, stride %d",
+                i, b.streamId, b.width, b.height, b.format, b.stride);
+        if (b.streamId > 0) {
+            if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+                // Assumes only one BLOB buffer type per capture
+                compressedBufferIndex = i;
+            } else {
+                ALOGV("Readout:    Sending image buffer %d (%p) to output stream %d",
+                        i, (void*)*(b.buffer), b.streamId);
+                GraphicBufferMapper::get().unlock(*(b.buffer));
+                const Stream &s = mParent->getStreamInfo(b.streamId);
+                res = s.ops->enqueue_buffer(s.ops, captureTime, b.buffer);
+                if (res != OK) {
+                    ALOGE("Error enqueuing image buffer %p: %s (%d)", b.buffer,
+                            strerror(-res), res);
+                    mParent->signalError();
+                }
+            }
+        }
+    }
+
+    if (compressedBufferIndex == -1) {
+        delete mBuffers;
+        mBuffers = NULL;
+    } else {
+        ALOGV("Readout:  Starting JPEG compression for buffer %d, stream %d",
+                compressedBufferIndex,
+                (*mBuffers)[compressedBufferIndex].streamId);
+        mParent->mJpegCompressor->start(mBuffers, captureTime);
+        mBuffers = NULL;
+    }
+
+    Mutex::Autolock l(mInputMutex);
+    mRequestCount--;
+    ALOGV("Readout: Done with request %d", frameNumber);
+    return true;
+}
+
+status_t EmulatedFakeCamera2::ReadoutThread::collectStatisticsMetadata(
+        camera_metadata_t *frame) {
+    // Completely fake face rectangles, don't correspond to real faces in scene
+    ALOGV("Readout:    Collecting statistics metadata");
+
+    status_t res;
+    camera_metadata_entry_t entry;
+    res = find_camera_metadata_entry(frame,
+                ANDROID_STATS_FACE_DETECT_MODE,
+                &entry);
+    if (res != OK) {
+        ALOGE("%s: Unable to find face detect mode!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (entry.data.u8[0] == ANDROID_STATS_FACE_DETECTION_OFF) return OK;
+
+    // The coordinate system for the face regions is the raw sensor pixel
+    // coordinates. Here, we map from the scene coordinates (0-19 in both axis)
+    // to raw pixels, for the scene defined in fake-pipeline2/Scene.cpp. We
+    // approximately place two faces on top of the windows of the house. No
+    // actual faces exist there, but might one day. Note that this doesn't
+    // account for the offsets used to account for aspect ratio differences, so
+    // the rectangles don't line up quite right.
+    const size_t numFaces = 2;
+    int32_t rects[numFaces * 4] = {
+            Sensor::kResolution[0] * 10 / 20,
+            Sensor::kResolution[1] * 15 / 20,
+            Sensor::kResolution[0] * 12 / 20,
+            Sensor::kResolution[1] * 17 / 20,
+
+            Sensor::kResolution[0] * 16 / 20,
+            Sensor::kResolution[1] * 15 / 20,
+            Sensor::kResolution[0] * 18 / 20,
+            Sensor::kResolution[1] * 17 / 20
+    };
+    // To simulate some kind of real detection going on, we jitter the rectangles on
+    // each frame by a few pixels in each dimension.
+    for (size_t i = 0; i < numFaces * 4; i++) {
+        rects[i] += (int32_t)(((float)rand() / RAND_MAX) * 6 - 3);
+    }
+    // The confidence scores (0-100) are similarly jittered.
+    uint8_t scores[numFaces] = { 85, 95 };
+    for (size_t i = 0; i < numFaces; i++) {
+        scores[i] += (int32_t)(((float)rand() / RAND_MAX) * 10 - 5);
+    }
+
+    res = add_camera_metadata_entry(frame, ANDROID_STATS_FACE_RECTANGLES,
+            rects, numFaces * 4);
+    if (res != OK) {
+        ALOGE("%s: Unable to add face rectangles!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    res = add_camera_metadata_entry(frame, ANDROID_STATS_FACE_SCORES,
+            scores, numFaces);
+    if (res != OK) {
+        ALOGE("%s: Unable to add face scores!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (entry.data.u8[0] == ANDROID_STATS_FACE_DETECTION_SIMPLE) return OK;
+
+    // Advanced face detection options - add eye/mouth coordinates.  The
+    // coordinates in order are (leftEyeX, leftEyeY, rightEyeX, rightEyeY,
+    // mouthX, mouthY). The mapping is the same as the face rectangles.
+    int32_t features[numFaces * 6] = {
+        Sensor::kResolution[0] * 10.5 / 20,
+        Sensor::kResolution[1] * 16 / 20,
+        Sensor::kResolution[0] * 11.5 / 20,
+        Sensor::kResolution[1] * 16 / 20,
+        Sensor::kResolution[0] * 11 / 20,
+        Sensor::kResolution[1] * 16.5 / 20,
+
+        Sensor::kResolution[0] * 16.5 / 20,
+        Sensor::kResolution[1] * 16 / 20,
+        Sensor::kResolution[0] * 17.5 / 20,
+        Sensor::kResolution[1] * 16 / 20,
+        Sensor::kResolution[0] * 17 / 20,
+        Sensor::kResolution[1] * 16.5 / 20,
+    };
+    // Jitter these a bit less than the rects
+    for (size_t i = 0; i < numFaces * 6; i++) {
+        features[i] += (int32_t)(((float)rand() / RAND_MAX) * 4 - 2);
+    }
+    // These are unique IDs that are used to identify each face while it's
+    // visible to the detector (if a face went away and came back, it'd get a
+    // new ID).
+    int32_t ids[numFaces] = {
+        100, 200
+    };
+
+    res = add_camera_metadata_entry(frame, ANDROID_STATS_FACE_LANDMARKS,
+            features, numFaces * 6);
+    if (res != OK) {
+        ALOGE("%s: Unable to add face landmarks!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    res = add_camera_metadata_entry(frame, ANDROID_STATS_FACE_IDS,
+            ids, numFaces);
+    if (res != OK) {
+        ALOGE("%s: Unable to add face scores!", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    return OK;
+}
+
+EmulatedFakeCamera2::ControlThread::ControlThread(EmulatedFakeCamera2 *parent):
+        Thread(false),
+        mParent(parent) {
+    mRunning = false;
+}
+
+EmulatedFakeCamera2::ControlThread::~ControlThread() {
+}
+
+status_t EmulatedFakeCamera2::ControlThread::readyToRun() {
+    Mutex::Autolock lock(mInputMutex);
+
+    ALOGV("Starting up ControlThread");
+    mRunning = true;
+    mStartAf = false;
+    mCancelAf = false;
+    mStartPrecapture = false;
+
+    mControlMode = ANDROID_CONTROL_AUTO;
+
+    mEffectMode = ANDROID_CONTROL_EFFECT_OFF;
+    mSceneMode = ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY;
+
+    mAfMode = ANDROID_CONTROL_AF_AUTO;
+    mAfModeChange = false;
+
+    mAeMode = ANDROID_CONTROL_AE_ON;
+    mAwbMode = ANDROID_CONTROL_AWB_AUTO;
+
+    mAfTriggerId = 0;
+    mPrecaptureTriggerId = 0;
+
+    mAfState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+    mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+    mAwbState = ANDROID_CONTROL_AWB_STATE_INACTIVE;
+
+    mExposureTime = kNormalExposureTime;
+
+    mInputSignal.signal();
+    return NO_ERROR;
+}
+
+status_t EmulatedFakeCamera2::ControlThread::waitUntilRunning() {
+    Mutex::Autolock lock(mInputMutex);
+    if (!mRunning) {
+        ALOGV("Waiting for control thread to start");
+        mInputSignal.wait(mInputMutex);
+    }
+    return OK;
+}
+
+status_t EmulatedFakeCamera2::ControlThread::processRequest(camera_metadata_t *request) {
+    Mutex::Autolock lock(mInputMutex);
+    // TODO: Add handling for all android.control.* fields here
+    camera_metadata_entry_t mode;
+    status_t res;
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_MODE,
+            &mode);
+    mControlMode = mode.data.u8[0];
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_EFFECT_MODE,
+            &mode);
+    mEffectMode = mode.data.u8[0];
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_SCENE_MODE,
+            &mode);
+    mSceneMode = mode.data.u8[0];
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_AF_MODE,
+            &mode);
+    if (mAfMode != mode.data.u8[0]) {
+        ALOGV("AF new mode: %d, old mode %d", mode.data.u8[0], mAfMode);
+        mAfMode = mode.data.u8[0];
+        mAfModeChange = true;
+        mStartAf = false;
+        mCancelAf = false;
+    }
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_AE_MODE,
+            &mode);
+    mAeMode = mode.data.u8[0];
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_AE_LOCK,
+            &mode);
+    bool aeLock = (mode.data.u8[0] == ANDROID_CONTROL_AE_LOCK_ON);
+    if (mAeLock && !aeLock) {
+        mAeState = ANDROID_CONTROL_AE_STATE_INACTIVE;
+    }
+    mAeLock = aeLock;
+
+    res = find_camera_metadata_entry(request,
+            ANDROID_CONTROL_AWB_MODE,
+            &mode);
+    mAwbMode = mode.data.u8[0];
+
+    // TODO: Override more control fields
+
+    if (mAeMode != ANDROID_CONTROL_AE_OFF) {
+        camera_metadata_entry_t exposureTime;
+        res = find_camera_metadata_entry(request,
+                ANDROID_SENSOR_EXPOSURE_TIME,
+                &exposureTime);
+        if (res == OK) {
+            exposureTime.data.i64[0] = mExposureTime;
+        }
+    }
+
+    return OK;
+}
+
+status_t EmulatedFakeCamera2::ControlThread::triggerAction(uint32_t msgType,
+        int32_t ext1, int32_t ext2) {
+    ALOGV("%s: Triggering %d (%d, %d)", __FUNCTION__, msgType, ext1, ext2);
+    Mutex::Autolock lock(mInputMutex);
+    switch (msgType) {
+        case CAMERA2_TRIGGER_AUTOFOCUS:
+            mAfTriggerId = ext1;
+            mStartAf = true;
+            mCancelAf = false;
+            break;
+        case CAMERA2_TRIGGER_CANCEL_AUTOFOCUS:
+            mAfTriggerId = ext1;
+            mStartAf = false;
+            mCancelAf = true;
+            break;
+        case CAMERA2_TRIGGER_PRECAPTURE_METERING:
+            mPrecaptureTriggerId = ext1;
+            mStartPrecapture = true;
+            break;
+        default:
+            ALOGE("%s: Unknown action triggered: %d (arguments %d %d)",
+                    __FUNCTION__, msgType, ext1, ext2);
+            return BAD_VALUE;
+    }
+    return OK;
+}
+
+const nsecs_t EmulatedFakeCamera2::ControlThread::kControlCycleDelay = 100 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinAfDuration = 500 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxAfDuration = 900 * MSEC;
+const float EmulatedFakeCamera2::ControlThread::kAfSuccessRate = 0.9;
+ // Once every 5 seconds
+const float EmulatedFakeCamera2::ControlThread::kContinuousAfStartRate =
+        kControlCycleDelay / 5.0 * SEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinAeDuration = 500 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxAeDuration = 2 * SEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinPrecaptureAeDuration = 100 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMaxPrecaptureAeDuration = 400 * MSEC;
+ // Once every 3 seconds
+const float EmulatedFakeCamera2::ControlThread::kAeScanStartRate =
+    kControlCycleDelay / 3000000000.0;
+
+const nsecs_t EmulatedFakeCamera2::ControlThread::kNormalExposureTime = 10 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kExposureJump = 2 * MSEC;
+const nsecs_t EmulatedFakeCamera2::ControlThread::kMinExposureTime = 1 * MSEC;
+
+bool EmulatedFakeCamera2::ControlThread::threadLoop() {
+    bool afModeChange = false;
+    bool afTriggered = false;
+    bool afCancelled = false;
+    uint8_t afState;
+    uint8_t afMode;
+    int32_t afTriggerId;
+    bool precaptureTriggered = false;
+    uint8_t aeState;
+    uint8_t aeMode;
+    bool    aeLock;
+    int32_t precaptureTriggerId;
+    nsecs_t nextSleep = kControlCycleDelay;
+
+    {
+        Mutex::Autolock lock(mInputMutex);
+        if (mStartAf) {
+            ALOGD("Starting AF trigger processing");
+            afTriggered = true;
+            mStartAf = false;
+        } else if (mCancelAf) {
+            ALOGD("Starting cancel AF trigger processing");
+            afCancelled = true;
+            mCancelAf = false;
+        }
+        afState = mAfState;
+        afMode = mAfMode;
+        afModeChange = mAfModeChange;
+        mAfModeChange = false;
+
+        afTriggerId = mAfTriggerId;
+
+        if(mStartPrecapture) {
+            ALOGD("Starting precapture trigger processing");
+            precaptureTriggered = true;
+            mStartPrecapture = false;
+        }
+        aeState = mAeState;
+        aeMode = mAeMode;
+        aeLock = mAeLock;
+        precaptureTriggerId = mPrecaptureTriggerId;
+    }
+
+    if (afCancelled || afModeChange) {
+        ALOGV("Resetting AF state due to cancel/mode change");
+        afState = ANDROID_CONTROL_AF_STATE_INACTIVE;
+        updateAfState(afState, afTriggerId);
+        mAfScanDuration = 0;
+        mLockAfterPassiveScan = false;
+    }
+
+    uint8_t oldAfState = afState;
+
+    if (afTriggered) {
+        afState = processAfTrigger(afMode, afState);
+    }
+
+    afState = maybeStartAfScan(afMode, afState);
+    afState = updateAfScan(afMode, afState, &nextSleep);
+    updateAfState(afState, afTriggerId);
+
+    if (precaptureTriggered) {
+        aeState = processPrecaptureTrigger(aeMode, aeState);
+    }
+
+    aeState = maybeStartAeScan(aeMode, aeLock, aeState);
+    aeState = updateAeScan(aeMode, aeLock, aeState, &nextSleep);
+    updateAeState(aeState, precaptureTriggerId);
+
+    int ret;
+    timespec t;
+    t.tv_sec = 0;
+    t.tv_nsec = nextSleep;
+    do {
+        ret = nanosleep(&t, &t);
+    } while (ret != 0);
+
+    if (mAfScanDuration > 0) {
+        mAfScanDuration -= nextSleep;
+    }
+    if (mAeScanDuration > 0) {
+        mAeScanDuration -= nextSleep;
+    }
+
+    return true;
+}
+
+int EmulatedFakeCamera2::ControlThread::processAfTrigger(uint8_t afMode,
+        uint8_t afState) {
+    switch (afMode) {
+        case ANDROID_CONTROL_AF_OFF:
+        case ANDROID_CONTROL_AF_EDOF:
+            // Do nothing
+            break;
+        case ANDROID_CONTROL_AF_MACRO:
+        case ANDROID_CONTROL_AF_AUTO:
+            switch (afState) {
+                case ANDROID_CONTROL_AF_STATE_INACTIVE:
+                case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+                case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                    // Start new focusing cycle
+                    mAfScanDuration =  ((double)rand() / RAND_MAX) *
+                        (kMaxAfDuration - kMinAfDuration) + kMinAfDuration;
+                    afState = ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN;
+                    ALOGV("%s: AF scan start, duration %lld ms",
+                          __FUNCTION__, mAfScanDuration / 1000000);
+                    break;
+                case ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN:
+                    // Ignore new request, already scanning
+                    break;
+                default:
+                    ALOGE("Unexpected AF state in AUTO/MACRO AF mode: %d",
+                          afState);
+            }
+            break;
+        case ANDROID_CONTROL_AF_CONTINUOUS_PICTURE:
+            switch (afState) {
+                // Picture mode waits for passive scan to complete
+                case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+                    mLockAfterPassiveScan = true;
+                    break;
+                case ANDROID_CONTROL_AF_STATE_INACTIVE:
+                    afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+                    break;
+                case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+                    afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+                    break;
+                case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+                case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                    // Must cancel to get out of these states
+                    break;
+                default:
+                    ALOGE("Unexpected AF state in CONTINUOUS_PICTURE AF mode: %d",
+                          afState);
+            }
+            break;
+        case ANDROID_CONTROL_AF_CONTINUOUS_VIDEO:
+            switch (afState) {
+                // Video mode does not wait for passive scan to complete
+                case ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN:
+                case ANDROID_CONTROL_AF_STATE_INACTIVE:
+                    afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+                    break;
+                case ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED:
+                    afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+                    break;
+                case ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED:
+                case ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                    // Must cancel to get out of these states
+                    break;
+                default:
+                    ALOGE("Unexpected AF state in CONTINUOUS_VIDEO AF mode: %d",
+                          afState);
+            }
+            break;
+        default:
+            break;
+    }
+    return afState;
+}
+
+int EmulatedFakeCamera2::ControlThread::maybeStartAfScan(uint8_t afMode,
+        uint8_t afState) {
+    if ((afMode == ANDROID_CONTROL_AF_CONTINUOUS_VIDEO ||
+            afMode == ANDROID_CONTROL_AF_CONTINUOUS_PICTURE) &&
+        (afState == ANDROID_CONTROL_AF_STATE_INACTIVE ||
+            afState == ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED)) {
+
+        bool startScan = ((double)rand() / RAND_MAX) < kContinuousAfStartRate;
+        if (startScan) {
+            // Start new passive focusing cycle
+            mAfScanDuration =  ((double)rand() / RAND_MAX) *
+                (kMaxAfDuration - kMinAfDuration) + kMinAfDuration;
+            afState = ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN;
+            ALOGV("%s: AF passive scan start, duration %lld ms",
+                __FUNCTION__, mAfScanDuration / 1000000);
+        }
+    }
+    return afState;
+}
+
+int EmulatedFakeCamera2::ControlThread::updateAfScan(uint8_t afMode,
+        uint8_t afState, nsecs_t *maxSleep) {
+    if (! (afState == ANDROID_CONTROL_AF_STATE_ACTIVE_SCAN ||
+            afState == ANDROID_CONTROL_AF_STATE_PASSIVE_SCAN ) ) {
+        return afState;
+    }
+
+    if (mAfScanDuration <= 0) {
+        ALOGV("%s: AF scan done", __FUNCTION__);
+        switch (afMode) {
+            case ANDROID_CONTROL_AF_MACRO:
+            case ANDROID_CONTROL_AF_AUTO: {
+                bool success = ((double)rand() / RAND_MAX) < kAfSuccessRate;
+                if (success) {
+                    afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+                } else {
+                    afState = ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
+                }
+                break;
+            }
+            case ANDROID_CONTROL_AF_CONTINUOUS_PICTURE:
+                if (mLockAfterPassiveScan) {
+                    afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED;
+                    mLockAfterPassiveScan = false;
+                } else {
+                    afState = ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED;
+                }
+                break;
+            case ANDROID_CONTROL_AF_CONTINUOUS_VIDEO:
+                afState = ANDROID_CONTROL_AF_STATE_PASSIVE_FOCUSED;
+                break;
+            default:
+                ALOGE("Unexpected AF mode in scan state");
+        }
+    } else {
+        if (mAfScanDuration <= *maxSleep) {
+            *maxSleep = mAfScanDuration;
+        }
+    }
+    return afState;
+}
+
+void EmulatedFakeCamera2::ControlThread::updateAfState(uint8_t newState,
+        int32_t triggerId) {
+    Mutex::Autolock lock(mInputMutex);
+    if (mAfState != newState) {
+        ALOGV("%s: Autofocus state now %d, id %d", __FUNCTION__,
+                newState, triggerId);
+        mAfState = newState;
+        mParent->sendNotification(CAMERA2_MSG_AUTOFOCUS,
+                newState, triggerId, 0);
+    }
+}
+
+int EmulatedFakeCamera2::ControlThread::processPrecaptureTrigger(uint8_t aeMode,
+        uint8_t aeState) {
+    switch (aeMode) {
+        case ANDROID_CONTROL_AE_OFF:
+            // Don't do anything for these
+            return aeState;
+        case ANDROID_CONTROL_AE_ON:
+        case ANDROID_CONTROL_AE_ON_AUTO_FLASH:
+        case ANDROID_CONTROL_AE_ON_ALWAYS_FLASH:
+        case ANDROID_CONTROL_AE_ON_AUTO_FLASH_REDEYE:
+            // Trigger a precapture cycle
+            aeState = ANDROID_CONTROL_AE_STATE_PRECAPTURE;
+            mAeScanDuration = ((double)rand() / RAND_MAX) *
+                    (kMaxPrecaptureAeDuration - kMinPrecaptureAeDuration) +
+                    kMinPrecaptureAeDuration;
+            ALOGD("%s: AE precapture scan start, duration %lld ms",
+                    __FUNCTION__, mAeScanDuration / 1000000);
+
+    }
+    return aeState;
+}
+
+int EmulatedFakeCamera2::ControlThread::maybeStartAeScan(uint8_t aeMode,
+        bool aeLocked,
+        uint8_t aeState) {
+    if (aeLocked) return aeState;
+    switch (aeMode) {
+        case ANDROID_CONTROL_AE_OFF:
+            break;
+        case ANDROID_CONTROL_AE_ON:
+        case ANDROID_CONTROL_AE_ON_AUTO_FLASH:
+        case ANDROID_CONTROL_AE_ON_ALWAYS_FLASH:
+        case ANDROID_CONTROL_AE_ON_AUTO_FLASH_REDEYE: {
+            if (aeState != ANDROID_CONTROL_AE_STATE_INACTIVE &&
+                    aeState != ANDROID_CONTROL_AE_STATE_CONVERGED) break;
+
+            bool startScan = ((double)rand() / RAND_MAX) < kAeScanStartRate;
+            if (startScan) {
+                mAeScanDuration = ((double)rand() / RAND_MAX) *
+                (kMaxAeDuration - kMinAeDuration) + kMinAeDuration;
+                aeState = ANDROID_CONTROL_AE_STATE_SEARCHING;
+                ALOGD("%s: AE scan start, duration %lld ms",
+                        __FUNCTION__, mAeScanDuration / 1000000);
+            }
+        }
+    }
+
+    return aeState;
+}
+
+int EmulatedFakeCamera2::ControlThread::updateAeScan(uint8_t aeMode,
+        bool aeLock, uint8_t aeState, nsecs_t *maxSleep) {
+    if (aeLock && aeState != ANDROID_CONTROL_AE_STATE_PRECAPTURE) {
+        mAeScanDuration = 0;
+        aeState = ANDROID_CONTROL_AE_STATE_LOCKED;
+    } else if ((aeState == ANDROID_CONTROL_AE_STATE_SEARCHING) ||
+            (aeState == ANDROID_CONTROL_AE_STATE_PRECAPTURE ) ) {
+        if (mAeScanDuration <= 0) {
+            ALOGD("%s: AE scan done", __FUNCTION__);
+            aeState = aeLock ?
+                    ANDROID_CONTROL_AE_STATE_LOCKED :ANDROID_CONTROL_AE_STATE_CONVERGED;
+
+            Mutex::Autolock lock(mInputMutex);
+            mExposureTime = kNormalExposureTime;
+        } else {
+            if (mAeScanDuration <= *maxSleep) {
+                *maxSleep = mAeScanDuration;
+            }
+
+            int64_t exposureDelta =
+                    ((double)rand() / RAND_MAX) * 2 * kExposureJump -
+                    kExposureJump;
+            Mutex::Autolock lock(mInputMutex);
+            mExposureTime = mExposureTime + exposureDelta;
+            if (mExposureTime < kMinExposureTime) mExposureTime = kMinExposureTime;
+        }
+    }
+
+    return aeState;
+}
+
+
+void EmulatedFakeCamera2::ControlThread::updateAeState(uint8_t newState,
+        int32_t triggerId) {
+    Mutex::Autolock lock(mInputMutex);
+    if (mAeState != newState) {
+        ALOGD("%s: Autoexposure state now %d, id %d", __FUNCTION__,
+                newState, triggerId);
+        mAeState = newState;
+        mParent->sendNotification(CAMERA2_MSG_AUTOEXPOSURE,
+                newState, triggerId, 0);
+    }
+}
+
+/** Private methods */
+
+status_t EmulatedFakeCamera2::constructStaticInfo(
+        camera_metadata_t **info,
+        bool sizeRequest) const {
+
+    size_t entryCount = 0;
+    size_t dataCount = 0;
+    status_t ret;
+
+#define ADD_OR_SIZE( tag, data, count ) \
+    if ( ( ret = addOrSize(*info, sizeRequest, &entryCount, &dataCount, \
+            tag, data, count) ) != OK ) return ret
+
+    // android.lens
+
+    // 5 cm min focus distance for back camera, infinity (fixed focus) for front
+    const float minFocusDistance = mFacingBack ? 1.0/0.05 : 0.0;
+    ADD_OR_SIZE(ANDROID_LENS_MINIMUM_FOCUS_DISTANCE,
+            &minFocusDistance, 1);
+    // 5 m hyperfocal distance for back camera, infinity (fixed focus) for front
+    const float hyperFocalDistance = mFacingBack ? 1.0/5.0 : 0.0;
+    ADD_OR_SIZE(ANDROID_LENS_HYPERFOCAL_DISTANCE,
+            &minFocusDistance, 1);
+
+    static const float focalLength = 3.30f; // mm
+    ADD_OR_SIZE(ANDROID_LENS_AVAILABLE_FOCAL_LENGTHS,
+            &focalLength, 1);
+    static const float aperture = 2.8f;
+    ADD_OR_SIZE(ANDROID_LENS_AVAILABLE_APERTURES,
+            &aperture, 1);
+    static const float filterDensity = 0;
+    ADD_OR_SIZE(ANDROID_LENS_AVAILABLE_FILTER_DENSITY,
+            &filterDensity, 1);
+    static const uint8_t availableOpticalStabilization =
+            ANDROID_LENS_OPTICAL_STABILIZATION_OFF;
+    ADD_OR_SIZE(ANDROID_LENS_AVAILABLE_OPTICAL_STABILIZATION,
+            &availableOpticalStabilization, 1);
+
+    static const int32_t lensShadingMapSize[] = {1, 1};
+    ADD_OR_SIZE(ANDROID_LENS_SHADING_MAP_SIZE, lensShadingMapSize,
+            sizeof(lensShadingMapSize)/sizeof(int32_t));
+
+    static const float lensShadingMap[3 * 1 * 1 ] =
+            { 1.f, 1.f, 1.f };
+    ADD_OR_SIZE(ANDROID_LENS_SHADING_MAP, lensShadingMap,
+            sizeof(lensShadingMap)/sizeof(float));
+
+    // Identity transform
+    static const int32_t geometricCorrectionMapSize[] = {2, 2};
+    ADD_OR_SIZE(ANDROID_LENS_GEOMETRIC_CORRECTION_MAP_SIZE,
+            geometricCorrectionMapSize,
+            sizeof(geometricCorrectionMapSize)/sizeof(int32_t));
+
+    static const float geometricCorrectionMap[2 * 3 * 2 * 2] = {
+            0.f, 0.f,  0.f, 0.f,  0.f, 0.f,
+            1.f, 0.f,  1.f, 0.f,  1.f, 0.f,
+            0.f, 1.f,  0.f, 1.f,  0.f, 1.f,
+            1.f, 1.f,  1.f, 1.f,  1.f, 1.f};
+    ADD_OR_SIZE(ANDROID_LENS_GEOMETRIC_CORRECTION_MAP,
+            geometricCorrectionMap,
+            sizeof(geometricCorrectionMap)/sizeof(float));
+
+    int32_t lensFacing = mFacingBack ?
+            ANDROID_LENS_FACING_BACK : ANDROID_LENS_FACING_FRONT;
+    ADD_OR_SIZE(ANDROID_LENS_FACING, &lensFacing, 1);
+
+    float lensPosition[3];
+    if (mFacingBack) {
+        // Back-facing camera is center-top on device
+        lensPosition[0] = 0;
+        lensPosition[1] = 20;
+        lensPosition[2] = -5;
+    } else {
+        // Front-facing camera is center-right on device
+        lensPosition[0] = 20;
+        lensPosition[1] = 20;
+        lensPosition[2] = 0;
+    }
+    ADD_OR_SIZE(ANDROID_LENS_POSITION, lensPosition, sizeof(lensPosition)/
+            sizeof(float));
+
+    // android.sensor
+
+    ADD_OR_SIZE(ANDROID_SENSOR_EXPOSURE_TIME_RANGE,
+            Sensor::kExposureTimeRange, 2);
+
+    ADD_OR_SIZE(ANDROID_SENSOR_MAX_FRAME_DURATION,
+            &Sensor::kFrameDurationRange[1], 1);
+
+    ADD_OR_SIZE(ANDROID_SENSOR_AVAILABLE_SENSITIVITIES,
+            Sensor::kAvailableSensitivities,
+            sizeof(Sensor::kAvailableSensitivities)
+            /sizeof(uint32_t));
+
+    ADD_OR_SIZE(ANDROID_SENSOR_COLOR_FILTER_ARRANGEMENT,
+            &Sensor::kColorFilterArrangement, 1);
+
+    static const float sensorPhysicalSize[2] = {3.20f, 2.40f}; // mm
+    ADD_OR_SIZE(ANDROID_SENSOR_PHYSICAL_SIZE,
+            sensorPhysicalSize, 2);
+
+    ADD_OR_SIZE(ANDROID_SENSOR_PIXEL_ARRAY_SIZE,
+            Sensor::kResolution, 2);
+
+    ADD_OR_SIZE(ANDROID_SENSOR_ACTIVE_ARRAY_SIZE,
+            Sensor::kResolution, 2);
+
+    ADD_OR_SIZE(ANDROID_SENSOR_WHITE_LEVEL,
+            &Sensor::kMaxRawValue, 1);
+
+    static const int32_t blackLevelPattern[4] = {
+            Sensor::kBlackLevel, Sensor::kBlackLevel,
+            Sensor::kBlackLevel, Sensor::kBlackLevel
+    };
+    ADD_OR_SIZE(ANDROID_SENSOR_BLACK_LEVEL_PATTERN,
+            blackLevelPattern, sizeof(blackLevelPattern)/sizeof(int32_t));
+
+    //TODO: sensor color calibration fields
+
+    // android.flash
+    static const uint8_t flashAvailable = 0;
+    ADD_OR_SIZE(ANDROID_FLASH_AVAILABLE, &flashAvailable, 1);
+
+    static const int64_t flashChargeDuration = 0;
+    ADD_OR_SIZE(ANDROID_FLASH_CHARGE_DURATION, &flashChargeDuration, 1);
+
+    // android.tonemap
+
+    static const int32_t tonemapCurvePoints = 128;
+    ADD_OR_SIZE(ANDROID_TONEMAP_MAX_CURVE_POINTS, &tonemapCurvePoints, 1);
+
+    // android.scaler
+
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_FORMATS,
+            kAvailableFormats,
+            sizeof(kAvailableFormats)/sizeof(uint32_t));
+
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_RAW_SIZES,
+            kAvailableRawSizes,
+            sizeof(kAvailableRawSizes)/sizeof(uint32_t));
+
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_RAW_MIN_DURATIONS,
+            kAvailableRawMinDurations,
+            sizeof(kAvailableRawMinDurations)/sizeof(uint64_t));
+
+    if (mFacingBack) {
+        ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES,
+                kAvailableProcessedSizesBack,
+                sizeof(kAvailableProcessedSizesBack)/sizeof(uint32_t));
+    } else {
+        ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_PROCESSED_SIZES,
+                kAvailableProcessedSizesFront,
+                sizeof(kAvailableProcessedSizesFront)/sizeof(uint32_t));
+    }
+
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS,
+            kAvailableProcessedMinDurations,
+            sizeof(kAvailableProcessedMinDurations)/sizeof(uint64_t));
+
+    if (mFacingBack) {
+        ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_JPEG_SIZES,
+                kAvailableJpegSizesBack,
+                sizeof(kAvailableJpegSizesBack)/sizeof(uint32_t));
+    } else {
+        ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_JPEG_SIZES,
+                kAvailableJpegSizesFront,
+                sizeof(kAvailableJpegSizesFront)/sizeof(uint32_t));
+    }
+
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_JPEG_MIN_DURATIONS,
+            kAvailableJpegMinDurations,
+            sizeof(kAvailableJpegMinDurations)/sizeof(uint64_t));
+
+    static const float maxZoom = 10;
+    ADD_OR_SIZE(ANDROID_SCALER_AVAILABLE_MAX_ZOOM,
+            &maxZoom, 1);
+
+    // android.jpeg
+
+    static const int32_t jpegThumbnailSizes[] = {
+            0, 0,
+            160, 120,
+            320, 240
+     };
+    ADD_OR_SIZE(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+            jpegThumbnailSizes, sizeof(jpegThumbnailSizes)/sizeof(int32_t));
+
+    static const int32_t jpegMaxSize = JpegCompressor::kMaxJpegSize;
+    ADD_OR_SIZE(ANDROID_JPEG_MAX_SIZE, &jpegMaxSize, 1);
+
+    // android.stats
+
+    static const uint8_t availableFaceDetectModes[] = {
+        ANDROID_STATS_FACE_DETECTION_OFF,
+        ANDROID_STATS_FACE_DETECTION_SIMPLE,
+        ANDROID_STATS_FACE_DETECTION_FULL
+    };
+
+    ADD_OR_SIZE(ANDROID_STATS_AVAILABLE_FACE_DETECT_MODES,
+            availableFaceDetectModes,
+            sizeof(availableFaceDetectModes));
+
+    static const int32_t maxFaceCount = 8;
+    ADD_OR_SIZE(ANDROID_STATS_MAX_FACE_COUNT,
+            &maxFaceCount, 1);
+
+    static const int32_t histogramSize = 64;
+    ADD_OR_SIZE(ANDROID_STATS_HISTOGRAM_BUCKET_COUNT,
+            &histogramSize, 1);
+
+    static const int32_t maxHistogramCount = 1000;
+    ADD_OR_SIZE(ANDROID_STATS_MAX_HISTOGRAM_COUNT,
+            &maxHistogramCount, 1);
+
+    static const int32_t sharpnessMapSize[2] = {64, 64};
+    ADD_OR_SIZE(ANDROID_STATS_SHARPNESS_MAP_SIZE,
+            sharpnessMapSize, sizeof(sharpnessMapSize)/sizeof(int32_t));
+
+    static const int32_t maxSharpnessMapValue = 1000;
+    ADD_OR_SIZE(ANDROID_STATS_MAX_SHARPNESS_MAP_VALUE,
+            &maxSharpnessMapValue, 1);
+
+    // android.control
+
+    static const uint8_t availableSceneModes[] = {
+            ANDROID_CONTROL_SCENE_MODE_UNSUPPORTED
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+            availableSceneModes, sizeof(availableSceneModes));
+
+    static const uint8_t availableEffects[] = {
+            ANDROID_CONTROL_EFFECT_OFF
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_EFFECTS,
+            availableEffects, sizeof(availableEffects));
+
+    int32_t max3aRegions = 0;
+    ADD_OR_SIZE(ANDROID_CONTROL_MAX_REGIONS,
+            &max3aRegions, 1);
+
+    static const uint8_t availableAeModes[] = {
+            ANDROID_CONTROL_AE_OFF,
+            ANDROID_CONTROL_AE_ON
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_MODES,
+            availableAeModes, sizeof(availableAeModes));
+
+    static const camera_metadata_rational exposureCompensationStep = {
+            1, 3
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_EXP_COMPENSATION_STEP,
+            &exposureCompensationStep, 1);
+
+    int32_t exposureCompensationRange[] = {-9, 9};
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_EXP_COMPENSATION_RANGE,
+            exposureCompensationRange,
+            sizeof(exposureCompensationRange)/sizeof(int32_t));
+
+    static const int32_t availableTargetFpsRanges[] = {
+            5, 30, 15, 30
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+            availableTargetFpsRanges,
+            sizeof(availableTargetFpsRanges)/sizeof(int32_t));
+
+    static const uint8_t availableAntibandingModes[] = {
+            ANDROID_CONTROL_AE_ANTIBANDING_OFF,
+            ANDROID_CONTROL_AE_ANTIBANDING_AUTO
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+            availableAntibandingModes, sizeof(availableAntibandingModes));
+
+    static const uint8_t availableAwbModes[] = {
+            ANDROID_CONTROL_AWB_OFF,
+            ANDROID_CONTROL_AWB_AUTO,
+            ANDROID_CONTROL_AWB_INCANDESCENT,
+            ANDROID_CONTROL_AWB_FLUORESCENT,
+            ANDROID_CONTROL_AWB_DAYLIGHT,
+            ANDROID_CONTROL_AWB_SHADE
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+            availableAwbModes, sizeof(availableAwbModes));
+
+    static const uint8_t availableAfModesBack[] = {
+            ANDROID_CONTROL_AF_OFF,
+            ANDROID_CONTROL_AF_AUTO,
+            ANDROID_CONTROL_AF_MACRO,
+            ANDROID_CONTROL_AF_CONTINUOUS_VIDEO,
+            ANDROID_CONTROL_AF_CONTINUOUS_PICTURE
+    };
+
+    static const uint8_t availableAfModesFront[] = {
+            ANDROID_CONTROL_AF_OFF
+    };
+
+    if (mFacingBack) {
+        ADD_OR_SIZE(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+                    availableAfModesBack, sizeof(availableAfModesBack));
+    } else {
+        ADD_OR_SIZE(ANDROID_CONTROL_AF_AVAILABLE_MODES,
+                    availableAfModesFront, sizeof(availableAfModesFront));
+    }
+
+    static const uint8_t availableVstabModes[] = {
+            ANDROID_CONTROL_VIDEO_STABILIZATION_OFF
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+            availableVstabModes, sizeof(availableVstabModes));
+
+#undef ADD_OR_SIZE
+    /** Allocate metadata if sizing */
+    if (sizeRequest) {
+        ALOGV("Allocating %d entries, %d extra bytes for "
+                "static camera info",
+                entryCount, dataCount);
+        *info = allocate_camera_metadata(entryCount, dataCount);
+        if (*info == NULL) {
+            ALOGE("Unable to allocate camera static info"
+                    "(%d entries, %d bytes extra data)",
+                    entryCount, dataCount);
+            return NO_MEMORY;
+        }
+    }
+    return OK;
+}
+
+status_t EmulatedFakeCamera2::constructDefaultRequest(
+        int request_template,
+        camera_metadata_t **request,
+        bool sizeRequest) const {
+
+    size_t entryCount = 0;
+    size_t dataCount = 0;
+    status_t ret;
+
+#define ADD_OR_SIZE( tag, data, count ) \
+    if ( ( ret = addOrSize(*request, sizeRequest, &entryCount, &dataCount, \
+            tag, data, count) ) != OK ) return ret
+
+    /** android.request */
+
+    static const uint8_t requestType = ANDROID_REQUEST_TYPE_CAPTURE;
+    ADD_OR_SIZE(ANDROID_REQUEST_TYPE, &requestType, 1);
+
+    static const uint8_t metadataMode = ANDROID_REQUEST_METADATA_FULL;
+    ADD_OR_SIZE(ANDROID_REQUEST_METADATA_MODE, &metadataMode, 1);
+
+    static const int32_t id = 0;
+    ADD_OR_SIZE(ANDROID_REQUEST_ID, &id, 1);
+
+    static const int32_t frameCount = 0;
+    ADD_OR_SIZE(ANDROID_REQUEST_FRAME_COUNT, &frameCount, 1);
+
+    // OUTPUT_STREAMS set by user
+    entryCount += 1;
+    dataCount += 5; // TODO: Should be maximum stream number
+
+    /** android.lens */
+
+    static const float focusDistance = 0;
+    ADD_OR_SIZE(ANDROID_LENS_FOCUS_DISTANCE, &focusDistance, 1);
+
+    static const float aperture = 2.8f;
+    ADD_OR_SIZE(ANDROID_LENS_APERTURE, &aperture, 1);
+
+    static const float focalLength = 5.0f;
+    ADD_OR_SIZE(ANDROID_LENS_FOCAL_LENGTH, &focalLength, 1);
+
+    static const float filterDensity = 0;
+    ADD_OR_SIZE(ANDROID_LENS_FILTER_DENSITY, &filterDensity, 1);
+
+    static const uint8_t opticalStabilizationMode =
+            ANDROID_LENS_OPTICAL_STABILIZATION_OFF;
+    ADD_OR_SIZE(ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+            &opticalStabilizationMode, 1);
+
+    // FOCUS_RANGE set only in frame
+
+    /** android.sensor */
+
+    static const int64_t exposureTime = 10 * MSEC;
+    ADD_OR_SIZE(ANDROID_SENSOR_EXPOSURE_TIME, &exposureTime, 1);
+
+    static const int64_t frameDuration = 33333333L; // 1/30 s
+    ADD_OR_SIZE(ANDROID_SENSOR_FRAME_DURATION, &frameDuration, 1);
+
+    static const int32_t sensitivity = 100;
+    ADD_OR_SIZE(ANDROID_SENSOR_SENSITIVITY, &sensitivity, 1);
+
+    // TIMESTAMP set only in frame
+
+    /** android.flash */
+
+    static const uint8_t flashMode = ANDROID_FLASH_OFF;
+    ADD_OR_SIZE(ANDROID_FLASH_MODE, &flashMode, 1);
+
+    static const uint8_t flashPower = 10;
+    ADD_OR_SIZE(ANDROID_FLASH_FIRING_POWER, &flashPower, 1);
+
+    static const int64_t firingTime = 0;
+    ADD_OR_SIZE(ANDROID_FLASH_FIRING_TIME, &firingTime, 1);
+
+    /** Processing block modes */
+    uint8_t hotPixelMode = 0;
+    uint8_t demosaicMode = 0;
+    uint8_t noiseMode = 0;
+    uint8_t shadingMode = 0;
+    uint8_t geometricMode = 0;
+    uint8_t colorMode = 0;
+    uint8_t tonemapMode = 0;
+    uint8_t edgeMode = 0;
+    switch (request_template) {
+      case CAMERA2_TEMPLATE_PREVIEW:
+        hotPixelMode = ANDROID_PROCESSING_FAST;
+        demosaicMode = ANDROID_PROCESSING_FAST;
+        noiseMode = ANDROID_PROCESSING_FAST;
+        shadingMode = ANDROID_PROCESSING_FAST;
+        geometricMode = ANDROID_PROCESSING_FAST;
+        colorMode = ANDROID_PROCESSING_FAST;
+        tonemapMode = ANDROID_PROCESSING_FAST;
+        edgeMode = ANDROID_PROCESSING_FAST;
+        break;
+      case CAMERA2_TEMPLATE_STILL_CAPTURE:
+        hotPixelMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        demosaicMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        noiseMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        shadingMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        geometricMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        colorMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        tonemapMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        edgeMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_RECORD:
+        hotPixelMode = ANDROID_PROCESSING_FAST;
+        demosaicMode = ANDROID_PROCESSING_FAST;
+        noiseMode = ANDROID_PROCESSING_FAST;
+        shadingMode = ANDROID_PROCESSING_FAST;
+        geometricMode = ANDROID_PROCESSING_FAST;
+        colorMode = ANDROID_PROCESSING_FAST;
+        tonemapMode = ANDROID_PROCESSING_FAST;
+        edgeMode = ANDROID_PROCESSING_FAST;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+        hotPixelMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        demosaicMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        noiseMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        shadingMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        geometricMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        colorMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        tonemapMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        edgeMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        break;
+      case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+        hotPixelMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        demosaicMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        noiseMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        shadingMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        geometricMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        colorMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        tonemapMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        edgeMode = ANDROID_PROCESSING_HIGH_QUALITY;
+        break;
+      default:
+        hotPixelMode = ANDROID_PROCESSING_FAST;
+        demosaicMode = ANDROID_PROCESSING_FAST;
+        noiseMode = ANDROID_PROCESSING_FAST;
+        shadingMode = ANDROID_PROCESSING_FAST;
+        geometricMode = ANDROID_PROCESSING_FAST;
+        colorMode = ANDROID_PROCESSING_FAST;
+        tonemapMode = ANDROID_PROCESSING_FAST;
+        edgeMode = ANDROID_PROCESSING_FAST;
+        break;
+    }
+    ADD_OR_SIZE(ANDROID_HOT_PIXEL_MODE, &hotPixelMode, 1);
+    ADD_OR_SIZE(ANDROID_DEMOSAIC_MODE, &demosaicMode, 1);
+    ADD_OR_SIZE(ANDROID_NOISE_MODE, &noiseMode, 1);
+    ADD_OR_SIZE(ANDROID_SHADING_MODE, &shadingMode, 1);
+    ADD_OR_SIZE(ANDROID_GEOMETRIC_MODE, &geometricMode, 1);
+    ADD_OR_SIZE(ANDROID_COLOR_MODE, &colorMode, 1);
+    ADD_OR_SIZE(ANDROID_TONEMAP_MODE, &tonemapMode, 1);
+    ADD_OR_SIZE(ANDROID_EDGE_MODE, &edgeMode, 1);
+
+    /** android.noise */
+    static const uint8_t noiseStrength = 5;
+    ADD_OR_SIZE(ANDROID_NOISE_STRENGTH, &noiseStrength, 1);
+
+    /** android.color */
+    static const float colorTransform[9] = {
+        1.0f, 0.f, 0.f,
+        0.f, 1.f, 0.f,
+        0.f, 0.f, 1.f
+    };
+    ADD_OR_SIZE(ANDROID_COLOR_TRANSFORM, colorTransform, 9);
+
+    /** android.tonemap */
+    static const float tonemapCurve[4] = {
+        0.f, 0.f,
+        1.f, 1.f
+    };
+    ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_RED, tonemapCurve, 4);
+    ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_GREEN, tonemapCurve, 4);
+    ADD_OR_SIZE(ANDROID_TONEMAP_CURVE_BLUE, tonemapCurve, 4);
+
+    /** android.edge */
+    static const uint8_t edgeStrength = 5;
+    ADD_OR_SIZE(ANDROID_EDGE_STRENGTH, &edgeStrength, 1);
+
+    /** android.scaler */
+    static const int32_t cropRegion[3] = {
+        0, 0, Sensor::kResolution[0]
+    };
+    ADD_OR_SIZE(ANDROID_SCALER_CROP_REGION, cropRegion, 3);
+
+    /** android.jpeg */
+    static const int32_t jpegQuality = 80;
+    ADD_OR_SIZE(ANDROID_JPEG_QUALITY, &jpegQuality, 1);
+
+    static const int32_t thumbnailSize[2] = {
+        640, 480
+    };
+    ADD_OR_SIZE(ANDROID_JPEG_THUMBNAIL_SIZE, thumbnailSize, 2);
+
+    static const int32_t thumbnailQuality = 80;
+    ADD_OR_SIZE(ANDROID_JPEG_THUMBNAIL_QUALITY, &thumbnailQuality, 1);
+
+    static const double gpsCoordinates[2] = {
+        0, 0
+    };
+    ADD_OR_SIZE(ANDROID_JPEG_GPS_COORDINATES, gpsCoordinates, 2);
+
+    static const uint8_t gpsProcessingMethod[32] = "None";
+    ADD_OR_SIZE(ANDROID_JPEG_GPS_PROCESSING_METHOD, gpsProcessingMethod, 32);
+
+    static const int64_t gpsTimestamp = 0;
+    ADD_OR_SIZE(ANDROID_JPEG_GPS_TIMESTAMP, &gpsTimestamp, 1);
+
+    static const int32_t jpegOrientation = 0;
+    ADD_OR_SIZE(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+
+    /** android.stats */
+
+    static const uint8_t faceDetectMode = ANDROID_STATS_FACE_DETECTION_OFF;
+    ADD_OR_SIZE(ANDROID_STATS_FACE_DETECT_MODE, &faceDetectMode, 1);
+
+    static const uint8_t histogramMode = ANDROID_STATS_OFF;
+    ADD_OR_SIZE(ANDROID_STATS_HISTOGRAM_MODE, &histogramMode, 1);
+
+    static const uint8_t sharpnessMapMode = ANDROID_STATS_OFF;
+    ADD_OR_SIZE(ANDROID_STATS_SHARPNESS_MAP_MODE, &sharpnessMapMode, 1);
+
+    // faceRectangles, faceScores, faceLandmarks, faceIds, histogram,
+    // sharpnessMap only in frames
+
+    /** android.control */
+
+    uint8_t controlIntent = 0;
+    switch (request_template) {
+      case CAMERA2_TEMPLATE_PREVIEW:
+        controlIntent = ANDROID_CONTROL_INTENT_PREVIEW;
+        break;
+      case CAMERA2_TEMPLATE_STILL_CAPTURE:
+        controlIntent = ANDROID_CONTROL_INTENT_STILL_CAPTURE;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_RECORD:
+        controlIntent = ANDROID_CONTROL_INTENT_VIDEO_RECORD;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+        controlIntent = ANDROID_CONTROL_INTENT_VIDEO_SNAPSHOT;
+        break;
+      case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+        controlIntent = ANDROID_CONTROL_INTENT_ZERO_SHUTTER_LAG;
+        break;
+      default:
+        controlIntent = ANDROID_CONTROL_INTENT_CUSTOM;
+        break;
+    }
+    ADD_OR_SIZE(ANDROID_CONTROL_CAPTURE_INTENT, &controlIntent, 1);
+
+    static const uint8_t controlMode = ANDROID_CONTROL_AUTO;
+    ADD_OR_SIZE(ANDROID_CONTROL_MODE, &controlMode, 1);
+
+    static const uint8_t effectMode = ANDROID_CONTROL_EFFECT_OFF;
+    ADD_OR_SIZE(ANDROID_CONTROL_EFFECT_MODE, &effectMode, 1);
+
+    static const uint8_t sceneMode = ANDROID_CONTROL_SCENE_MODE_FACE_PRIORITY;
+    ADD_OR_SIZE(ANDROID_CONTROL_SCENE_MODE, &sceneMode, 1);
+
+    static const uint8_t aeMode = ANDROID_CONTROL_AE_ON_AUTO_FLASH;
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_MODE, &aeMode, 1);
+
+    static const uint8_t aeLock = ANDROID_CONTROL_AE_LOCK_OFF;
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_LOCK, &aeLock, 1);
+
+    static const int32_t controlRegions[5] = {
+        0, 0, Sensor::kResolution[0], Sensor::kResolution[1], 1000
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_REGIONS, controlRegions, 5);
+
+    static const int32_t aeExpCompensation = 0;
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_EXP_COMPENSATION, &aeExpCompensation, 1);
+
+    static const int32_t aeTargetFpsRange[2] = {
+        10, 30
+    };
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_TARGET_FPS_RANGE, aeTargetFpsRange, 2);
+
+    static const uint8_t aeAntibandingMode =
+            ANDROID_CONTROL_AE_ANTIBANDING_AUTO;
+    ADD_OR_SIZE(ANDROID_CONTROL_AE_ANTIBANDING_MODE, &aeAntibandingMode, 1);
+
+    static const uint8_t awbMode =
+            ANDROID_CONTROL_AWB_AUTO;
+    ADD_OR_SIZE(ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
+
+    static const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF;
+    ADD_OR_SIZE(ANDROID_CONTROL_AWB_LOCK, &awbLock, 1);
+
+    ADD_OR_SIZE(ANDROID_CONTROL_AWB_REGIONS, controlRegions, 5);
+
+    uint8_t afMode = 0;
+    switch (request_template) {
+      case CAMERA2_TEMPLATE_PREVIEW:
+        afMode = ANDROID_CONTROL_AF_AUTO;
+        break;
+      case CAMERA2_TEMPLATE_STILL_CAPTURE:
+        afMode = ANDROID_CONTROL_AF_AUTO;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_RECORD:
+        afMode = ANDROID_CONTROL_AF_CONTINUOUS_VIDEO;
+        break;
+      case CAMERA2_TEMPLATE_VIDEO_SNAPSHOT:
+        afMode = ANDROID_CONTROL_AF_CONTINUOUS_VIDEO;
+        break;
+      case CAMERA2_TEMPLATE_ZERO_SHUTTER_LAG:
+        afMode = ANDROID_CONTROL_AF_CONTINUOUS_PICTURE;
+        break;
+      default:
+        afMode = ANDROID_CONTROL_AF_AUTO;
+        break;
+    }
+    ADD_OR_SIZE(ANDROID_CONTROL_AF_MODE, &afMode, 1);
+
+    ADD_OR_SIZE(ANDROID_CONTROL_AF_REGIONS, controlRegions, 5);
+
+    static const uint8_t vstabMode = ANDROID_CONTROL_VIDEO_STABILIZATION_OFF;
+    ADD_OR_SIZE(ANDROID_CONTROL_VIDEO_STABILIZATION_MODE, &vstabMode, 1);
+
+    // aeState, awbState, afState only in frame
+
+    /** Allocate metadata if sizing */
+    if (sizeRequest) {
+        ALOGV("Allocating %d entries, %d extra bytes for "
+                "request template type %d",
+                entryCount, dataCount, request_template);
+        *request = allocate_camera_metadata(entryCount, dataCount);
+        if (*request == NULL) {
+            ALOGE("Unable to allocate new request template type %d "
+                    "(%d entries, %d bytes extra data)", request_template,
+                    entryCount, dataCount);
+            return NO_MEMORY;
+        }
+    }
+    return OK;
+#undef ADD_OR_SIZE
+}
+
+status_t EmulatedFakeCamera2::addOrSize(camera_metadata_t *request,
+        bool sizeRequest,
+        size_t *entryCount,
+        size_t *dataCount,
+        uint32_t tag,
+        const void *entryData,
+        size_t entryDataCount) {
+    status_t res;
+    if (!sizeRequest) {
+        return add_camera_metadata_entry(request, tag, entryData,
+                entryDataCount);
+    } else {
+        int type = get_camera_metadata_tag_type(tag);
+        if (type < 0 ) return BAD_VALUE;
+        (*entryCount)++;
+        (*dataCount) += calculate_camera_metadata_entry_data_size(type,
+                entryDataCount);
+        return OK;
+    }
+}
+
+bool EmulatedFakeCamera2::isStreamInUse(uint32_t id) {
+    // Assumes mMutex is locked; otherwise new requests could enter
+    // configureThread while readoutThread is being checked
+
+    // Order of isStreamInUse calls matters
+    if (mConfigureThread->isStreamInUse(id) ||
+            mReadoutThread->isStreamInUse(id) ||
+            mJpegCompressor->isStreamInUse(id) ) {
+        ALOGE("%s: Stream %d is in use in active requests!",
+                __FUNCTION__, id);
+        return true;
+    }
+    return false;
+}
+
+bool EmulatedFakeCamera2::isReprocessStreamInUse(uint32_t id) {
+    // TODO: implement
+    return false;
+}
+
+const Stream& EmulatedFakeCamera2::getStreamInfo(uint32_t streamId) {
+    Mutex::Autolock lock(mMutex);
+
+    return mStreams.valueFor(streamId);
+}
+
+const ReprocessStream& EmulatedFakeCamera2::getReprocessStreamInfo(uint32_t streamId) {
+    Mutex::Autolock lock(mMutex);
+
+    return mReprocessStreams.valueFor(streamId);
+}
+
 };  /* namespace android */
diff --git a/tools/emulator/system/camera/EmulatedFakeCamera2.h b/tools/emulator/system/camera/EmulatedFakeCamera2.h
index 89b12d3..ee47235 100644
--- a/tools/emulator/system/camera/EmulatedFakeCamera2.h
+++ b/tools/emulator/system/camera/EmulatedFakeCamera2.h
@@ -24,6 +24,13 @@
  */
 
 #include "EmulatedCamera2.h"
+#include "fake-pipeline2/Base.h"
+#include "fake-pipeline2/Sensor.h"
+#include "fake-pipeline2/JpegCompressor.h"
+#include <utils/Condition.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
 
 namespace android {
 
@@ -44,22 +51,372 @@
 
 public:
     /* Initializes EmulatedFakeCamera2 instance. */
-     status_t Initialize();
+    status_t Initialize();
 
     /****************************************************************************
-     * EmulatedCamera abstract API implementation.
+     * Camera Module API and generic hardware device API implementation
+     ***************************************************************************/
+public:
+
+    virtual status_t connectCamera(hw_device_t** device);
+
+    virtual status_t closeCamera();
+
+    virtual status_t getCameraInfo(struct camera_info *info);
+
+    /****************************************************************************
+     * EmulatedCamera2 abstract API implementation.
+     ***************************************************************************/
+protected:
+    /** Request input queue */
+
+    virtual int requestQueueNotify();
+
+    /** Count of requests in flight */
+    virtual int getInProgressCount();
+
+    /** Cancel all captures in flight */
+    //virtual int flushCapturesInProgress();
+
+    /** Construct default request */
+    virtual int constructDefaultRequest(
+            int request_template,
+            camera_metadata_t **request);
+
+    virtual int allocateStream(
+            uint32_t width,
+            uint32_t height,
+            int format,
+            const camera2_stream_ops_t *stream_ops,
+            uint32_t *stream_id,
+            uint32_t *format_actual,
+            uint32_t *usage,
+            uint32_t *max_buffers);
+
+    virtual int registerStreamBuffers(
+            uint32_t stream_id,
+            int num_buffers,
+            buffer_handle_t *buffers);
+
+    virtual int releaseStream(uint32_t stream_id);
+
+    // virtual int allocateReprocessStream(
+    //         uint32_t width,
+    //         uint32_t height,
+    //         uint32_t format,
+    //         const camera2_stream_ops_t *stream_ops,
+    //         uint32_t *stream_id,
+    //         uint32_t *format_actual,
+    //         uint32_t *usage,
+    //         uint32_t *max_buffers);
+
+    virtual int allocateReprocessStreamFromStream(
+            uint32_t output_stream_id,
+            const camera2_stream_in_ops_t *stream_ops,
+            uint32_t *stream_id);
+
+    virtual int releaseReprocessStream(uint32_t stream_id);
+
+    virtual int triggerAction(uint32_t trigger_id,
+            int32_t ext1,
+            int32_t ext2);
+
+    /** Custom tag definitions */
+    virtual const char* getVendorSectionName(uint32_t tag);
+    virtual const char* getVendorTagName(uint32_t tag);
+    virtual int         getVendorTagType(uint32_t tag);
+
+    /** Debug methods */
+
+    virtual int dump(int fd);
+
+public:
+    /****************************************************************************
+     * Utility methods called by configure/readout threads and pipeline
      ***************************************************************************/
 
-protected:
+    // Get information about a given stream. Will lock mMutex
+    const Stream &getStreamInfo(uint32_t streamId);
+    const ReprocessStream &getReprocessStreamInfo(uint32_t streamId);
+
+    // Notifies rest of camera subsystem of serious error
+    void signalError();
+
+private:
+    /****************************************************************************
+     * Utility methods
+     ***************************************************************************/
+    /** Construct static camera metadata, two-pass */
+    status_t constructStaticInfo(
+            camera_metadata_t **info,
+            bool sizeRequest) const;
+
+    /** Two-pass implementation of constructDefaultRequest */
+    status_t constructDefaultRequest(
+            int request_template,
+            camera_metadata_t **request,
+            bool sizeRequest) const;
+    /** Helper function for constructDefaultRequest */
+    static status_t addOrSize( camera_metadata_t *request,
+            bool sizeRequest,
+            size_t *entryCount,
+            size_t *dataCount,
+            uint32_t tag,
+            const void *entry_data,
+            size_t entry_count);
+
+    /** Determine if the stream id is listed in any currently-in-flight
+     * requests. Assumes mMutex is locked */
+    bool isStreamInUse(uint32_t streamId);
+
+    /** Determine if the reprocess stream id is listed in any
+     * currently-in-flight requests. Assumes mMutex is locked */
+    bool isReprocessStreamInUse(uint32_t streamId);
 
     /****************************************************************************
-     * Data memebers.
+     * Pipeline controller threads
+     ***************************************************************************/
+
+    class ConfigureThread: public Thread {
+      public:
+        ConfigureThread(EmulatedFakeCamera2 *parent);
+        ~ConfigureThread();
+
+        status_t waitUntilRunning();
+        status_t newRequestAvailable();
+        status_t readyToRun();
+
+        bool isStreamInUse(uint32_t id);
+        int getInProgressCount();
+      private:
+        EmulatedFakeCamera2 *mParent;
+        static const nsecs_t kWaitPerLoop = 10000000L; // 10 ms
+
+        bool mRunning;
+        bool threadLoop();
+
+        bool setupCapture();
+        bool setupReprocess();
+
+        bool configureNextCapture();
+        bool configureNextReprocess();
+
+        bool getBuffers();
+
+        Mutex mInputMutex; // Protects mActive, mRequestCount
+        Condition mInputSignal;
+        bool mActive; // Whether we're waiting for input requests or actively
+                      // working on them
+        size_t mRequestCount;
+
+        camera_metadata_t *mRequest;
+
+        Mutex mInternalsMutex; // Lock before accessing below members.
+        bool    mWaitingForReadout;
+        bool    mNextNeedsJpeg;
+        bool    mNextIsCapture;
+        int32_t mNextFrameNumber;
+        int64_t mNextExposureTime;
+        int64_t mNextFrameDuration;
+        int32_t mNextSensitivity;
+        Buffers *mNextBuffers;
+    };
+
+    class ReadoutThread: public Thread {
+      public:
+        ReadoutThread(EmulatedFakeCamera2 *parent);
+        ~ReadoutThread();
+
+        status_t readyToRun();
+
+        // Input
+        status_t waitUntilRunning();
+        bool waitForReady(nsecs_t timeout);
+        void setNextOperation(bool isCapture,
+                camera_metadata_t *request,
+                Buffers *buffers);
+        bool isStreamInUse(uint32_t id);
+        int getInProgressCount();
+      private:
+        EmulatedFakeCamera2 *mParent;
+
+        bool mRunning;
+        bool threadLoop();
+
+        bool readyForNextCapture();
+        status_t collectStatisticsMetadata(camera_metadata_t *frame);
+
+        // Inputs
+        Mutex mInputMutex; // Protects mActive, mInFlightQueue, mRequestCount
+        Condition mInputSignal;
+        Condition mReadySignal;
+
+        bool mActive;
+
+        static const int kInFlightQueueSize = 4;
+        struct InFlightQueue {
+            bool isCapture;
+            camera_metadata_t *request;
+            Buffers *buffers;
+        } *mInFlightQueue;
+
+        size_t mInFlightHead;
+        size_t mInFlightTail;
+
+        size_t mRequestCount;
+
+        // Internals
+        Mutex mInternalsMutex;
+
+        bool mIsCapture;
+        camera_metadata_t *mRequest;
+        Buffers *mBuffers;
+
+    };
+
+    // 3A management thread (auto-exposure, focus, white balance)
+    class ControlThread: public Thread {
+      public:
+        ControlThread(EmulatedFakeCamera2 *parent);
+        ~ControlThread();
+
+        status_t readyToRun();
+
+        status_t waitUntilRunning();
+
+        // Interpret request's control parameters and override
+        // capture settings as needed
+        status_t processRequest(camera_metadata_t *request);
+
+        status_t triggerAction(uint32_t msgType,
+                int32_t ext1, int32_t ext2);
+      private:
+        ControlThread(const ControlThread &t);
+        ControlThread& operator=(const ControlThread &t);
+
+        // Constants controlling fake 3A behavior
+        static const nsecs_t kControlCycleDelay;
+        static const nsecs_t kMinAfDuration;
+        static const nsecs_t kMaxAfDuration;
+        static const float kAfSuccessRate;
+        static const float kContinuousAfStartRate;
+
+        static const float kAeScanStartRate;
+        static const nsecs_t kMinAeDuration;
+        static const nsecs_t kMaxAeDuration;
+        static const nsecs_t kMinPrecaptureAeDuration;
+        static const nsecs_t kMaxPrecaptureAeDuration;
+
+        static const nsecs_t kNormalExposureTime;
+        static const nsecs_t kExposureJump;
+        static const nsecs_t kMinExposureTime;
+
+        EmulatedFakeCamera2 *mParent;
+
+        bool mRunning;
+        bool threadLoop();
+
+        Mutex mInputMutex; // Protects input methods
+        Condition mInputSignal;
+
+        // Trigger notifications
+        bool mStartAf;
+        bool mCancelAf;
+        bool mStartPrecapture;
+
+        // Latest state for 3A request fields
+        uint8_t mControlMode;
+
+        uint8_t mEffectMode;
+        uint8_t mSceneMode;
+
+        uint8_t mAfMode;
+        bool mAfModeChange;
+
+        uint8_t mAwbMode;
+        uint8_t mAeMode;
+
+        // Latest trigger IDs
+        int32_t mAfTriggerId;
+        int32_t mPrecaptureTriggerId;
+
+        // Current state for 3A algorithms
+        uint8_t mAfState;
+        uint8_t mAeState;
+        uint8_t mAwbState;
+        bool    mAeLock;
+
+        // Current control parameters
+        nsecs_t mExposureTime;
+
+        // Private to threadLoop and its utility methods
+
+        nsecs_t mAfScanDuration;
+        nsecs_t mAeScanDuration;
+        bool mLockAfterPassiveScan;
+
+        // Utility methods for AF
+        int processAfTrigger(uint8_t afMode, uint8_t afState);
+        int maybeStartAfScan(uint8_t afMode, uint8_t afState);
+        int updateAfScan(uint8_t afMode, uint8_t afState, nsecs_t *maxSleep);
+        void updateAfState(uint8_t newState, int32_t triggerId);
+
+        // Utility methods for precapture trigger
+        int processPrecaptureTrigger(uint8_t aeMode, uint8_t aeState);
+        int maybeStartAeScan(uint8_t aeMode, bool aeLock, uint8_t aeState);
+        int updateAeScan(uint8_t aeMode, bool aeLock, uint8_t aeState,
+                nsecs_t *maxSleep);
+        void updateAeState(uint8_t newState, int32_t triggerId);
+    };
+
+    /****************************************************************************
+     * Static configuration information
+     ***************************************************************************/
+private:
+    static const uint32_t kMaxRawStreamCount = 1;
+    static const uint32_t kMaxProcessedStreamCount = 3;
+    static const uint32_t kMaxJpegStreamCount = 1;
+    static const uint32_t kMaxReprocessStreamCount = 2;
+    static const uint32_t kMaxBufferCount = 4;
+    static const uint32_t kAvailableFormats[];
+    static const uint32_t kAvailableRawSizes[];
+    static const uint64_t kAvailableRawMinDurations[];
+    static const uint32_t kAvailableProcessedSizesBack[];
+    static const uint32_t kAvailableProcessedSizesFront[];
+    static const uint64_t kAvailableProcessedMinDurations[];
+    static const uint32_t kAvailableJpegSizesBack[];
+    static const uint32_t kAvailableJpegSizesFront[];
+    static const uint64_t kAvailableJpegMinDurations[];
+
+    /****************************************************************************
+     * Data members.
      ***************************************************************************/
 
 protected:
     /* Facing back (true) or front (false) switch. */
-    bool                        mFacingBack;
+    bool mFacingBack;
 
+private:
+    /** Stream manipulation */
+    uint32_t mNextStreamId;
+    uint32_t mRawStreamCount;
+    uint32_t mProcessedStreamCount;
+    uint32_t mJpegStreamCount;
+
+    uint32_t mNextReprocessStreamId;
+    uint32_t mReprocessStreamCount;
+
+    KeyedVector<uint32_t, Stream> mStreams;
+    KeyedVector<uint32_t, ReprocessStream> mReprocessStreams;
+
+    /** Simulated hardware interfaces */
+    sp<Sensor> mSensor;
+    sp<JpegCompressor> mJpegCompressor;
+
+    /** Pipeline control threads */
+    sp<ConfigureThread> mConfigureThread;
+    sp<ReadoutThread>   mReadoutThread;
+    sp<ControlThread>   mControlThread;
 };
 
 }; /* namespace android */
diff --git a/tools/emulator/system/camera/fake-pipeline2/Base.h b/tools/emulator/system/camera/fake-pipeline2/Base.h
new file mode 100644
index 0000000..057629b
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Base.h
@@ -0,0 +1,64 @@
+/*
+ * 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 file includes various basic structures that are needed by multiple parts
+ * of the fake camera 2 implementation.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_BASE_H
+#define HW_EMULATOR_CAMERA2_BASE_H
+
+#include <system/window.h>
+#include <hardware/camera2.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+
+/* Internal structure for passing buffers across threads */
+struct StreamBuffer {
+    // Positive numbers are output streams
+    // Negative numbers are input reprocess streams
+    // Zero is an auxillary buffer
+    int streamId;
+    uint32_t width, height;
+    uint32_t format;
+    uint32_t stride;
+    buffer_handle_t *buffer;
+    uint8_t *img;
+};
+typedef Vector<StreamBuffer> Buffers;
+
+struct Stream {
+    const camera2_stream_ops_t *ops;
+    uint32_t width, height;
+    int32_t format;
+    uint32_t stride;
+};
+
+struct ReprocessStream {
+    const camera2_stream_in_ops_t *ops;
+    uint32_t width, height;
+    int32_t format;
+    uint32_t stride;
+    // -1 if the reprocessing stream is independent
+    int32_t sourceStreamId;
+};
+
+} // namespace android;
+
+#endif
diff --git a/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp
new file mode 100644
index 0000000..20b9634
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera2_JpegCompressor"
+
+#include <utils/Log.h>
+#include <ui/GraphicBufferMapper.h>
+
+#include "JpegCompressor.h"
+#include "../EmulatedFakeCamera2.h"
+
+namespace android {
+
+JpegCompressor::JpegCompressor(EmulatedFakeCamera2 *parent):
+        Thread(false),
+        mIsBusy(false),
+        mParent(parent),
+        mBuffers(NULL),
+        mCaptureTime(0) {
+}
+
+JpegCompressor::~JpegCompressor() {
+    Mutex::Autolock lock(mMutex);
+}
+
+status_t JpegCompressor::start(Buffers *buffers,
+        nsecs_t captureTime) {
+    Mutex::Autolock lock(mMutex);
+    {
+        Mutex::Autolock busyLock(mBusyMutex);
+
+        if (mIsBusy) {
+            ALOGE("%s: Already processing a buffer!", __FUNCTION__);
+            return INVALID_OPERATION;
+        }
+
+        mIsBusy = true;
+
+        mBuffers = buffers;
+        mCaptureTime = captureTime;
+    }
+
+    status_t res;
+    res = run("EmulatedFakeCamera2::JpegCompressor");
+    if (res != OK) {
+        ALOGE("%s: Unable to start up compression thread: %s (%d)",
+                __FUNCTION__, strerror(-res), res);
+        delete mBuffers;
+    }
+    return res;
+}
+
+status_t JpegCompressor::cancel() {
+    requestExitAndWait();
+    return OK;
+}
+
+status_t JpegCompressor::readyToRun() {
+    return OK;
+}
+
+bool JpegCompressor::threadLoop() {
+    Mutex::Autolock lock(mMutex);
+    ALOGV("%s: Starting compression thread", __FUNCTION__);
+
+    // Find source and target buffers. Assumes only one buffer matches
+    // each condition!
+
+    bool foundJpeg = false, mFoundAux = false;
+    for (size_t i = 0; i < mBuffers->size(); i++) {
+        const StreamBuffer &b = (*mBuffers)[i];
+        if (b.format == HAL_PIXEL_FORMAT_BLOB) {
+            mJpegBuffer = b;
+            mFoundJpeg = true;
+        } else if (b.streamId <= 0) {
+            mAuxBuffer = b;
+            mFoundAux = true;
+        }
+        if (mFoundJpeg && mFoundAux) break;
+    }
+    if (!mFoundJpeg || !mFoundAux) {
+        ALOGE("%s: Unable to find buffers for JPEG source/destination",
+                __FUNCTION__);
+        cleanUp();
+        return false;
+    }
+
+    // Set up error management
+
+    mJpegErrorInfo = NULL;
+    JpegError error;
+    error.parent = this;
+
+    mCInfo.err = jpeg_std_error(&error);
+    mCInfo.err->error_exit = jpegErrorHandler;
+
+    jpeg_create_compress(&mCInfo);
+    if (checkError("Error initializing compression")) return false;
+
+    // Route compressed data straight to output stream buffer
+
+    JpegDestination jpegDestMgr;
+    jpegDestMgr.parent = this;
+    jpegDestMgr.init_destination = jpegInitDestination;
+    jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
+    jpegDestMgr.term_destination = jpegTermDestination;
+
+    mCInfo.dest = &jpegDestMgr;
+
+    // Set up compression parameters
+
+    mCInfo.image_width = mAuxBuffer.width;
+    mCInfo.image_height = mAuxBuffer.height;
+    mCInfo.input_components = 3;
+    mCInfo.in_color_space = JCS_RGB;
+
+    jpeg_set_defaults(&mCInfo);
+    if (checkError("Error configuring defaults")) return false;
+
+    // Do compression
+
+    jpeg_start_compress(&mCInfo, TRUE);
+    if (checkError("Error starting compression")) return false;
+
+    size_t rowStride = mAuxBuffer.stride * 3;
+    const size_t kChunkSize = 32;
+    while (mCInfo.next_scanline < mCInfo.image_height) {
+        JSAMPROW chunk[kChunkSize];
+        for (size_t i = 0 ; i < kChunkSize; i++) {
+            chunk[i] = (JSAMPROW)
+                    (mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
+        }
+        jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
+        if (checkError("Error while compressing")) return false;
+        if (exitPending()) {
+            ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
+            cleanUp();
+            return false;
+        }
+    }
+
+    jpeg_finish_compress(&mCInfo);
+    if (checkError("Error while finishing compression")) return false;
+
+    // Write to JPEG output stream
+
+    ALOGV("%s: Compression complete, pushing to stream %d", __FUNCTION__,
+          mJpegBuffer.streamId);
+
+    GraphicBufferMapper::get().unlock(*(mJpegBuffer.buffer));
+    status_t res;
+    const Stream &s = mParent->getStreamInfo(mJpegBuffer.streamId);
+    res = s.ops->enqueue_buffer(s.ops, mCaptureTime, mJpegBuffer.buffer);
+    if (res != OK) {
+        ALOGE("%s: Error queueing compressed image buffer %p: %s (%d)",
+                __FUNCTION__, mJpegBuffer.buffer, strerror(-res), res);
+        mParent->signalError();
+    }
+
+    // All done
+
+    cleanUp();
+
+    return false;
+}
+
+bool JpegCompressor::isBusy() {
+    Mutex::Autolock busyLock(mBusyMutex);
+    return mIsBusy;
+}
+
+bool JpegCompressor::isStreamInUse(uint32_t id) {
+    Mutex::Autolock lock(mBusyMutex);
+
+    if (mBuffers && mIsBusy) {
+        for (size_t i = 0; i < mBuffers->size(); i++) {
+            if ( (*mBuffers)[i].streamId == (int)id ) return true;
+        }
+    }
+    return false;
+}
+
+bool JpegCompressor::waitForDone(nsecs_t timeout) {
+    Mutex::Autolock lock(mBusyMutex);
+    status_t res = OK;
+    if (mIsBusy) {
+        res = mDone.waitRelative(mBusyMutex, timeout);
+    }
+    return (res == OK);
+}
+
+bool JpegCompressor::checkError(const char *msg) {
+    if (mJpegErrorInfo) {
+        char errBuffer[JMSG_LENGTH_MAX];
+        mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
+        ALOGE("%s: %s: %s",
+                __FUNCTION__, msg, errBuffer);
+        cleanUp();
+        mJpegErrorInfo = NULL;
+        return true;
+    }
+    return false;
+}
+
+void JpegCompressor::cleanUp() {
+    status_t res;
+    jpeg_destroy_compress(&mCInfo);
+    Mutex::Autolock lock(mBusyMutex);
+
+    if (mFoundAux) {
+        if (mAuxBuffer.streamId == 0) {
+            delete[] mAuxBuffer.img;
+        } else {
+            GraphicBufferMapper::get().unlock(*(mAuxBuffer.buffer));
+            const ReprocessStream &s =
+                    mParent->getReprocessStreamInfo(-mAuxBuffer.streamId);
+            res = s.ops->release_buffer(s.ops, mAuxBuffer.buffer);
+            if (res != OK) {
+                ALOGE("Error releasing reprocess buffer %p: %s (%d)",
+                        mAuxBuffer.buffer, strerror(-res), res);
+                mParent->signalError();
+            }
+        }
+    }
+    delete mBuffers;
+    mBuffers = NULL;
+
+    mIsBusy = false;
+    mDone.signal();
+}
+
+void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
+    JpegError *error = static_cast<JpegError*>(cinfo->err);
+    error->parent->mJpegErrorInfo = cinfo;
+}
+
+void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
+    JpegDestination *dest= static_cast<JpegDestination*>(cinfo->dest);
+    ALOGV("%s: Setting destination to %p, size %d",
+            __FUNCTION__, dest->parent->mJpegBuffer.img, kMaxJpegSize);
+    dest->next_output_byte = (JOCTET*)(dest->parent->mJpegBuffer.img);
+    dest->free_in_buffer = kMaxJpegSize;
+}
+
+boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr cinfo) {
+    ALOGE("%s: JPEG destination buffer overflow!",
+            __FUNCTION__);
+    return true;
+}
+
+void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
+    ALOGV("%s: Done writing JPEG data. %d bytes left in buffer",
+            __FUNCTION__, cinfo->dest->free_in_buffer);
+}
+
+} // namespace android
diff --git a/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h
new file mode 100644
index 0000000..ea2a84f
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/JpegCompressor.h
@@ -0,0 +1,109 @@
+/*
+ * 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 simulates a hardware JPEG compressor.  It receives image buffers
+ * in RGBA_8888 format, processes them in a worker thread, and then pushes them
+ * out to their destination stream.
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_JPEG_H
+#define HW_EMULATOR_CAMERA2_JPEG_H
+
+#include "utils/Thread.h"
+#include "utils/Mutex.h"
+#include "utils/Timers.h"
+
+#include "Base.h"
+
+#include <stdio.h>
+
+extern "C" {
+#include <jpeglib.h>
+}
+
+namespace android {
+
+class EmulatedFakeCamera2;
+
+class JpegCompressor: private Thread, public virtual RefBase {
+  public:
+
+    JpegCompressor(EmulatedFakeCamera2 *parent);
+    ~JpegCompressor();
+
+    // Start compressing COMPRESSED format buffers; JpegCompressor takes
+    // ownership of the Buffers vector.
+    status_t start(Buffers *buffers,
+            nsecs_t captureTime);
+
+    status_t cancel();
+
+    bool isBusy();
+    bool isStreamInUse(uint32_t id);
+
+    bool waitForDone(nsecs_t timeout);
+
+    // TODO: Measure this
+    static const size_t kMaxJpegSize = 300000;
+
+  private:
+    Mutex mBusyMutex;
+    bool mIsBusy;
+    Condition mDone;
+
+    Mutex mMutex;
+
+    EmulatedFakeCamera2 *mParent;
+
+    Buffers *mBuffers;
+    nsecs_t mCaptureTime;
+
+    StreamBuffer mJpegBuffer, mAuxBuffer;
+    bool mFoundJpeg, mFoundAux;
+
+    jpeg_compress_struct mCInfo;
+
+    struct JpegError : public jpeg_error_mgr {
+        JpegCompressor *parent;
+    };
+    j_common_ptr mJpegErrorInfo;
+
+    struct JpegDestination : public jpeg_destination_mgr {
+        JpegCompressor *parent;
+    };
+
+    static void jpegErrorHandler(j_common_ptr cinfo);
+
+    static void jpegInitDestination(j_compress_ptr cinfo);
+    static boolean jpegEmptyOutputBuffer(j_compress_ptr cinfo);
+    static void jpegTermDestination(j_compress_ptr cinfo);
+
+    bool checkError(const char *msg);
+    void cleanUp();
+
+    /**
+     * Inherited Thread virtual overrides
+     */
+  private:
+    virtual status_t readyToRun();
+    virtual bool threadLoop();
+};
+
+} // namespace android
+
+#endif
diff --git a/tools/emulator/system/camera/fake-pipeline2/Scene.cpp b/tools/emulator/system/camera/fake-pipeline2/Scene.cpp
new file mode 100644
index 0000000..ca50350
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Scene.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Scene"
+#include <utils/Log.h>
+#include <stdlib.h>
+
+#include "Scene.h"
+
+// TODO: This should probably be done host-side in OpenGL for speed and better
+// quality
+
+namespace android {
+
+// Define single-letter shortcuts for scene definition, for directly indexing
+// mCurrentColors
+#define G (Scene::GRASS * Scene::NUM_CHANNELS)
+#define S (Scene::GRASS_SHADOW * Scene::NUM_CHANNELS)
+#define H (Scene::HILL * Scene::NUM_CHANNELS)
+#define W (Scene::WALL * Scene::NUM_CHANNELS)
+#define R (Scene::ROOF * Scene::NUM_CHANNELS)
+#define D (Scene::DOOR * Scene::NUM_CHANNELS)
+#define C (Scene::CHIMNEY * Scene::NUM_CHANNELS)
+#define I (Scene::WINDOW * Scene::NUM_CHANNELS)
+#define U (Scene::SUN * Scene::NUM_CHANNELS)
+#define K (Scene::SKY * Scene::NUM_CHANNELS)
+#define M (Scene::MOON * Scene::NUM_CHANNELS)
+
+const int Scene::kSceneWidth = 20;
+const int Scene::kSceneHeight = 20;
+
+const uint8_t Scene::kScene[Scene::kSceneWidth * Scene::kSceneHeight] = {
+    //      5         10        15        20
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K, // 5
+    K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,K,
+    K,K,K,K,K,K,K,K,H,H,H,H,H,H,H,H,H,H,H,H,
+    K,K,K,K,K,K,K,K,H,H,H,H,H,H,H,C,C,H,H,H,
+    K,K,K,K,K,K,H,H,H,H,H,H,H,H,H,C,C,H,H,H,
+    H,K,K,K,K,K,H,R,R,R,R,R,R,R,R,R,R,R,R,H, // 10
+    H,K,K,K,K,H,H,R,R,R,R,R,R,R,R,R,R,R,R,H,
+    H,H,H,K,K,H,H,R,R,R,R,R,R,R,R,R,R,R,R,H,
+    H,H,H,K,K,H,H,H,W,W,W,W,W,W,W,W,W,W,H,H,
+    S,S,S,G,G,S,S,S,W,W,W,W,W,W,W,W,W,W,S,S,
+    S,G,G,G,G,S,S,S,W,I,I,W,D,D,W,I,I,W,S,S, // 15
+    G,G,G,G,G,G,S,S,W,I,I,W,D,D,W,I,I,W,S,S,
+    G,G,G,G,G,G,G,G,W,W,W,W,D,D,W,W,W,W,G,G,
+    G,G,G,G,G,G,G,G,W,W,W,W,D,D,W,W,W,W,G,G,
+    G,G,G,G,G,G,G,G,S,S,S,S,S,S,S,S,S,S,G,G,
+    G,G,G,G,G,G,G,G,S,S,S,S,S,S,S,S,S,S,G,G, // 20
+    //      5         10        15        20
+};
+
+#undef G
+#undef S
+#undef H
+#undef W
+#undef R
+#undef D
+#undef C
+#undef I
+#undef U
+#undef K
+#undef M
+
+Scene::Scene(
+    int sensorWidthPx,
+    int sensorHeightPx,
+    float sensorSensitivity):
+        mSensorWidth(sensorWidthPx),
+        mSensorHeight(sensorHeightPx),
+        mHour(12),
+        mExposureDuration(0.033f),
+        mSensorSensitivity(sensorSensitivity)
+{
+    // Map scene to sensor pixels
+    if (mSensorWidth > mSensorHeight) {
+        mMapDiv = (mSensorWidth / (kSceneWidth + 1) ) + 1;
+    } else {
+        mMapDiv = (mSensorHeight / (kSceneHeight + 1) ) + 1;
+    }
+    mOffsetX = (kSceneWidth * mMapDiv - mSensorWidth) / 2;
+    mOffsetY = (kSceneHeight * mMapDiv - mSensorHeight) / 2;
+
+    // Assume that sensor filters are sRGB primaries to start
+    mFilterR[0]  =  3.2406f; mFilterR[1]  = -1.5372f; mFilterR[2]  = -0.4986f;
+    mFilterGr[0] = -0.9689f; mFilterGr[1] =  1.8758f; mFilterGr[2] =  0.0415f;
+    mFilterGb[0] = -0.9689f; mFilterGb[1] =  1.8758f; mFilterGb[2] =  0.0415f;
+    mFilterB[0]  =  0.0557f; mFilterB[1]  = -0.2040f; mFilterB[2]  =  1.0570f;
+
+
+}
+
+Scene::~Scene() {
+}
+
+void Scene::setColorFilterXYZ(
+        float rX, float rY, float rZ,
+        float grX, float grY, float grZ,
+        float gbX, float gbY, float gbZ,
+        float bX, float bY, float bZ) {
+    mFilterR[0]  = rX;  mFilterR[1]  = rY;  mFilterR[2]  = rZ;
+    mFilterGr[0] = grX; mFilterGr[1] = grY; mFilterGr[2] = grZ;
+    mFilterGb[0] = gbX; mFilterGb[1] = gbY; mFilterGb[2] = gbZ;
+    mFilterB[0]  = bX;  mFilterB[1]  = bY;  mFilterB[2]  = bZ;
+}
+
+void Scene::setHour(int hour) {
+    ALOGV("Hour set to: %d", hour);
+    mHour = hour % 24;
+}
+
+int Scene::getHour() {
+    return mHour;
+}
+
+void Scene::setExposureDuration(float seconds) {
+    mExposureDuration = seconds;
+}
+
+void Scene::calculateScene(nsecs_t time) {
+    // Calculate time fractions for interpolation
+    int timeIdx = mHour / kTimeStep;
+    int nextTimeIdx = (timeIdx + 1) % (24 / kTimeStep);
+    const nsecs_t kOneHourInNsec = 1e9 * 60 * 60;
+    nsecs_t timeSinceIdx = (mHour - timeIdx * kTimeStep) * kOneHourInNsec + time;
+    float timeFrac = timeSinceIdx / (float)(kOneHourInNsec * kTimeStep);
+
+    // Determine overall sunlight levels
+    float sunLux =
+            kSunlight[timeIdx] * (1 - timeFrac) +
+            kSunlight[nextTimeIdx] * timeFrac;
+    ALOGV("Sun lux: %f", sunLux);
+
+    float sunShadeLux = sunLux * (kDaylightShadeIllum / kDirectSunIllum);
+
+    // Determine sun/shade illumination chromaticity
+    float currentSunXY[2];
+    float currentShadeXY[2];
+
+    const float *prevSunXY, *nextSunXY;
+    const float *prevShadeXY, *nextShadeXY;
+    if (kSunlight[timeIdx] == kSunsetIllum ||
+            kSunlight[timeIdx] == kTwilightIllum) {
+        prevSunXY = kSunsetXY;
+        prevShadeXY = kSunsetXY;
+    } else {
+        prevSunXY = kDirectSunlightXY;
+        prevShadeXY = kDaylightXY;
+    }
+    if (kSunlight[nextTimeIdx] == kSunsetIllum ||
+            kSunlight[nextTimeIdx] == kTwilightIllum) {
+        nextSunXY = kSunsetXY;
+        nextShadeXY = kSunsetXY;
+    } else {
+        nextSunXY = kDirectSunlightXY;
+        nextShadeXY = kDaylightXY;
+    }
+    currentSunXY[0] = prevSunXY[0] * (1 - timeFrac) +
+            nextSunXY[0] * timeFrac;
+    currentSunXY[1] = prevSunXY[1] * (1 - timeFrac) +
+            nextSunXY[1] * timeFrac;
+
+    currentShadeXY[0] = prevShadeXY[0] * (1 - timeFrac) +
+            nextShadeXY[0] * timeFrac;
+    currentShadeXY[1] = prevShadeXY[1] * (1 - timeFrac) +
+            nextShadeXY[1] * timeFrac;
+
+    ALOGV("Sun XY: %f, %f, Shade XY: %f, %f",
+            currentSunXY[0], currentSunXY[1],
+            currentShadeXY[0], currentShadeXY[1]);
+
+    // Converting for xyY to XYZ:
+    // X = Y / y * x
+    // Y = Y
+    // Z = Y / y * (1 - x - y);
+    float sunXYZ[3] = {
+        sunLux / currentSunXY[1] * currentSunXY[0],
+        sunLux,
+        sunLux / currentSunXY[1] *
+        (1 - currentSunXY[0] - currentSunXY[1])
+    };
+    float sunShadeXYZ[3] = {
+        sunShadeLux / currentShadeXY[1] * currentShadeXY[0],
+        sunShadeLux,
+        sunShadeLux / currentShadeXY[1] *
+        (1 - currentShadeXY[0] - currentShadeXY[1])
+    };
+    ALOGV("Sun XYZ: %f, %f, %f",
+            sunXYZ[0], sunXYZ[1], sunXYZ[2]);
+    ALOGV("Sun shade XYZ: %f, %f, %f",
+            sunShadeXYZ[0], sunShadeXYZ[1], sunShadeXYZ[2]);
+
+    // Determine moonlight levels
+    float moonLux =
+            kMoonlight[timeIdx] * (1 - timeFrac) +
+            kMoonlight[nextTimeIdx] * timeFrac;
+    float moonShadeLux = moonLux * (kDaylightShadeIllum / kDirectSunIllum);
+
+    float moonXYZ[3] = {
+        moonLux / kMoonlightXY[1] * kMoonlightXY[0],
+        moonLux,
+        moonLux / kMoonlightXY[1] *
+        (1 - kMoonlightXY[0] - kMoonlightXY[1])
+    };
+    float moonShadeXYZ[3] = {
+        moonShadeLux / kMoonlightXY[1] * kMoonlightXY[0],
+        moonShadeLux,
+        moonShadeLux / kMoonlightXY[1] *
+        (1 - kMoonlightXY[0] - kMoonlightXY[1])
+    };
+
+    // Determine starlight level
+    const float kClearNightXYZ[3] = {
+        kClearNightIllum / kMoonlightXY[1] * kMoonlightXY[0],
+        kClearNightIllum,
+        kClearNightIllum / kMoonlightXY[1] *
+            (1 - kMoonlightXY[0] - kMoonlightXY[1])
+    };
+
+    // Calculate direct and shaded light
+    float directIllumXYZ[3] = {
+        sunXYZ[0] + moonXYZ[0] + kClearNightXYZ[0],
+        sunXYZ[1] + moonXYZ[1] + kClearNightXYZ[1],
+        sunXYZ[2] + moonXYZ[2] + kClearNightXYZ[2],
+    };
+
+    float shadeIllumXYZ[3] = {
+        kClearNightXYZ[0],
+        kClearNightXYZ[1],
+        kClearNightXYZ[2]
+    };
+
+    shadeIllumXYZ[0] += (mHour < kSunOverhead) ? sunXYZ[0] : sunShadeXYZ[0];
+    shadeIllumXYZ[1] += (mHour < kSunOverhead) ? sunXYZ[1] : sunShadeXYZ[1];
+    shadeIllumXYZ[2] += (mHour < kSunOverhead) ? sunXYZ[2] : sunShadeXYZ[2];
+
+    // Moon up period covers 23->0 transition, shift for simplicity
+    int adjHour = (mHour + 12) % 24;
+    int adjMoonOverhead = (kMoonOverhead + 12 ) % 24;
+    shadeIllumXYZ[0] += (adjHour < adjMoonOverhead) ?
+            moonXYZ[0] : moonShadeXYZ[0];
+    shadeIllumXYZ[1] += (adjHour < adjMoonOverhead) ?
+            moonXYZ[1] : moonShadeXYZ[1];
+    shadeIllumXYZ[2] += (adjHour < adjMoonOverhead) ?
+            moonXYZ[2] : moonShadeXYZ[2];
+
+    ALOGV("Direct XYZ: %f, %f, %f",
+            directIllumXYZ[0],directIllumXYZ[1],directIllumXYZ[2]);
+    ALOGV("Shade XYZ: %f, %f, %f",
+            shadeIllumXYZ[0], shadeIllumXYZ[1], shadeIllumXYZ[2]);
+
+    for (int i = 0; i < NUM_MATERIALS; i++) {
+        // Converting for xyY to XYZ:
+        // X = Y / y * x
+        // Y = Y
+        // Z = Y / y * (1 - x - y);
+        float matXYZ[3] = {
+            kMaterials_xyY[i][2] / kMaterials_xyY[i][1] *
+              kMaterials_xyY[i][0],
+            kMaterials_xyY[i][2],
+            kMaterials_xyY[i][2] / kMaterials_xyY[i][1] *
+              (1 - kMaterials_xyY[i][0] - kMaterials_xyY[i][1])
+        };
+
+        if (kMaterialsFlags[i] == 0 || kMaterialsFlags[i] & kSky) {
+            matXYZ[0] *= directIllumXYZ[0];
+            matXYZ[1] *= directIllumXYZ[1];
+            matXYZ[2] *= directIllumXYZ[2];
+        } else if (kMaterialsFlags[i] & kShadowed) {
+            matXYZ[0] *= shadeIllumXYZ[0];
+            matXYZ[1] *= shadeIllumXYZ[1];
+            matXYZ[2] *= shadeIllumXYZ[2];
+        } // else if (kMaterialsFlags[i] * kSelfLit), do nothing
+
+        ALOGV("Mat %d XYZ: %f, %f, %f", i, matXYZ[0], matXYZ[1], matXYZ[2]);
+        float luxToElectrons = mSensorSensitivity * mExposureDuration /
+                (kAperture * kAperture);
+        mCurrentColors[i*NUM_CHANNELS + 0] =
+                (mFilterR[0] * matXYZ[0] +
+                 mFilterR[1] * matXYZ[1] +
+                 mFilterR[2] * matXYZ[2])
+                * luxToElectrons;
+        mCurrentColors[i*NUM_CHANNELS + 1] =
+                (mFilterGr[0] * matXYZ[0] +
+                 mFilterGr[1] * matXYZ[1] +
+                 mFilterGr[2] * matXYZ[2])
+                * luxToElectrons;
+        mCurrentColors[i*NUM_CHANNELS + 2] =
+                (mFilterGb[0] * matXYZ[0] +
+                 mFilterGb[1] * matXYZ[1] +
+                 mFilterGb[2] * matXYZ[2])
+                * luxToElectrons;
+        mCurrentColors[i*NUM_CHANNELS + 3] =
+                (mFilterB[0] * matXYZ[0] +
+                 mFilterB[1] * matXYZ[1] +
+                 mFilterB[2] * matXYZ[2])
+                * luxToElectrons;
+
+        ALOGV("Color %d RGGB: %d, %d, %d, %d", i,
+                mCurrentColors[i*NUM_CHANNELS + 0],
+                mCurrentColors[i*NUM_CHANNELS + 1],
+                mCurrentColors[i*NUM_CHANNELS + 2],
+                mCurrentColors[i*NUM_CHANNELS + 3]);
+    }
+    // Shake viewpoint
+    mHandshakeX = rand() % mMapDiv/4 - mMapDiv/8;
+    mHandshakeY = rand() % mMapDiv/4 - mMapDiv/8;
+    // Set starting pixel
+    setReadoutPixel(0,0);
+}
+
+void Scene::setReadoutPixel(int x, int y) {
+    mCurrentX = x;
+    mCurrentY = y;
+    mSubX = (x + mOffsetX + mHandshakeX) % mMapDiv;
+    mSubY = (y + mOffsetY + mHandshakeY) % mMapDiv;
+    mSceneX = (x + mOffsetX + mHandshakeX) / mMapDiv;
+    mSceneY = (y + mOffsetY + mHandshakeY) / mMapDiv;
+    mSceneIdx = mSceneY * kSceneWidth + mSceneX;
+    mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
+}
+
+const uint32_t* Scene::getPixelElectrons() {
+    const uint32_t *pixel = mCurrentSceneMaterial;
+    mCurrentX++;
+    mSubX++;
+    if (mCurrentX >= mSensorWidth) {
+        mCurrentX = 0;
+        mCurrentY++;
+        if (mCurrentY >= mSensorHeight) mCurrentY = 0;
+        setReadoutPixel(mCurrentX, mCurrentY);
+    } else if (mSubX > mMapDiv) {
+        mSceneIdx++;
+        mSceneX++;
+        mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
+        mSubX = 0;
+    }
+    return pixel;
+}
+
+// RGB->YUV, Jpeg standard
+const float Scene::kRgb2Yuv[12] = {
+       0.299f,    0.587f,    0.114f,    0.f,
+    -0.16874f, -0.33126f,      0.5f, -128.f,
+         0.5f, -0.41869f, -0.08131f, -128.f,
+};
+
+// Aperture of imaging lens
+const float Scene::kAperture = 2.8;
+
+// Sun illumination levels through the day
+const float Scene::kSunlight[24/kTimeStep] =
+{
+    0, // 00:00
+    0,
+    0,
+    kTwilightIllum, // 06:00
+    kDirectSunIllum,
+    kDirectSunIllum,
+    kDirectSunIllum, // 12:00
+    kDirectSunIllum,
+    kDirectSunIllum,
+    kSunsetIllum, // 18:00
+    kTwilightIllum,
+    0
+};
+
+// Moon illumination levels through the day
+const float Scene::kMoonlight[24/kTimeStep] =
+{
+    kFullMoonIllum, // 00:00
+    kFullMoonIllum,
+    0,
+    0, // 06:00
+    0,
+    0,
+    0, // 12:00
+    0,
+    0,
+    0, // 18:00
+    0,
+    kFullMoonIllum
+};
+
+const int Scene::kSunOverhead = 12;
+const int Scene::kMoonOverhead = 0;
+
+// Used for sun illumination levels
+const float Scene::kDirectSunIllum     = 100000;
+const float Scene::kSunsetIllum        = 400;
+const float Scene::kTwilightIllum      = 4;
+// Used for moon illumination levels
+const float Scene::kFullMoonIllum      = 1;
+// Other illumination levels
+const float Scene::kDaylightShadeIllum = 20000;
+const float Scene::kClearNightIllum    = 2e-3;
+const float Scene::kStarIllum          = 2e-6;
+const float Scene::kLivingRoomIllum    = 50;
+
+const float Scene::kIncandescentXY[2]   = { 0.44757f, 0.40745f};
+const float Scene::kDirectSunlightXY[2] = { 0.34842f, 0.35161f};
+const float Scene::kDaylightXY[2]       = { 0.31271f, 0.32902f};
+const float Scene::kNoonSkyXY[2]        = { 0.346f,   0.359f};
+const float Scene::kMoonlightXY[2]      = { 0.34842f, 0.35161f};
+const float Scene::kSunsetXY[2]         = { 0.527f,   0.413f};
+
+const uint8_t Scene::kSelfLit  = 0x01;
+const uint8_t Scene::kShadowed = 0x02;
+const uint8_t Scene::kSky      = 0x04;
+
+// For non-self-lit materials, the Y component is normalized with 1=full
+// reflectance; for self-lit materials, it's the constant illuminance in lux.
+const float Scene::kMaterials_xyY[Scene::NUM_MATERIALS][3] = {
+    { 0.3688f, 0.4501f, .1329f }, // GRASS
+    { 0.3688f, 0.4501f, .1329f }, // GRASS_SHADOW
+    { 0.3986f, 0.5002f, .4440f }, // HILL
+    { 0.3262f, 0.5040f, .2297f }, // WALL
+    { 0.4336f, 0.3787f, .1029f }, // ROOF
+    { 0.3316f, 0.2544f, .0639f }, // DOOR
+    { 0.3425f, 0.3577f, .0887f }, // CHIMNEY
+    { kIncandescentXY[0], kIncandescentXY[1], kLivingRoomIllum }, // WINDOW
+    { kDirectSunlightXY[0], kDirectSunlightXY[1], kDirectSunIllum }, // SUN
+    { kNoonSkyXY[0], kNoonSkyXY[1], kDaylightShadeIllum / kDirectSunIllum }, // SKY
+    { kMoonlightXY[0], kMoonlightXY[1], kFullMoonIllum } // MOON
+};
+
+const uint8_t Scene::kMaterialsFlags[Scene::NUM_MATERIALS] = {
+    0,
+    kShadowed,
+    kShadowed,
+    kShadowed,
+    kShadowed,
+    kShadowed,
+    kShadowed,
+    kSelfLit,
+    kSelfLit,
+    kSky,
+    kSelfLit,
+};
+
+} // namespace android
diff --git a/tools/emulator/system/camera/fake-pipeline2/Scene.h b/tools/emulator/system/camera/fake-pipeline2/Scene.h
new file mode 100644
index 0000000..687e427
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Scene.h
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+/**
+ * The Scene class implements a simple physical simulation of a scene, using the
+ * CIE 1931 colorspace to represent light in physical units (lux).
+ *
+ * It's fairly approximate, but does provide a scene with realistic widely
+ * variable illumination levels and colors over time.
+ *
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_SCENE_H
+#define HW_EMULATOR_CAMERA2_SCENE_H
+
+#include "utils/Timers.h"
+
+namespace android {
+
+class Scene {
+  public:
+    Scene(int sensorWidthPx,
+            int sensorHeightPx,
+            float sensorSensitivity);
+    ~Scene();
+
+    // Set the filter coefficients for the red, green, and blue filters on the
+    // sensor. Used as an optimization to pre-calculate various illuminance
+    // values. Two different green filters can be provided, to account for
+    // possible cross-talk on a Bayer sensor. Must be called before
+    // calculateScene.
+    void setColorFilterXYZ(
+        float rX, float rY, float rZ,
+        float grX, float grY, float grZ,
+        float gbX, float gbY, float gbZ,
+        float bX, float bY, float bZ);
+
+    // Set time of day (24-hour clock). This controls the general light levels
+    // in the scene. Must be called before calculateScene
+    void setHour(int hour);
+    // Get current hour
+    int getHour();
+
+    // Set the duration of exposure for determining luminous exposure.
+    // Must be called before calculateScene
+    void setExposureDuration(float seconds);
+
+    // Calculate scene information for current hour and the time offset since
+    // the hour. Must be called at least once before calling getLuminousExposure.
+    // Resets pixel readout location to 0,0
+    void calculateScene(nsecs_t time);
+
+    // Set sensor pixel readout location.
+    void setReadoutPixel(int x, int y);
+
+    // Get sensor response in physical units (electrons) for light hitting the
+    // current readout pixel, after passing through color filters. The readout
+    // pixel will be auto-incremented. The returned array can be indexed with
+    // ColorChannels.
+    const uint32_t* getPixelElectrons();
+
+    enum ColorChannels {
+        R = 0,
+        Gr,
+        Gb,
+        B,
+        Y,
+        Cb,
+        Cr,
+        NUM_CHANNELS
+    };
+
+  private:
+    // Sensor color filtering coefficients in XYZ
+    float mFilterR[3];
+    float mFilterGr[3];
+    float mFilterGb[3];
+    float mFilterB[3];
+
+    int mOffsetX, mOffsetY;
+    int mMapDiv;
+
+    int mHandshakeX, mHandshakeY;
+
+    int mSensorWidth;
+    int mSensorHeight;
+    int mCurrentX;
+    int mCurrentY;
+    int mSubX;
+    int mSubY;
+    int mSceneX;
+    int mSceneY;
+    int mSceneIdx;
+    uint32_t *mCurrentSceneMaterial;
+
+    int mHour;
+    float mExposureDuration;
+    float mSensorSensitivity;
+
+    enum Materials {
+        GRASS = 0,
+        GRASS_SHADOW,
+        HILL,
+        WALL,
+        ROOF,
+        DOOR,
+        CHIMNEY,
+        WINDOW,
+        SUN,
+        SKY,
+        MOON,
+        NUM_MATERIALS
+    };
+
+    uint32_t mCurrentColors[NUM_MATERIALS*NUM_CHANNELS];
+
+    /**
+     * Constants for scene definition. These are various degrees of approximate.
+     */
+
+    // RGB->YUV conversion
+    static const float kRgb2Yuv[12];
+
+    // Aperture of imaging lens
+    static const float kAperture;
+
+    // Sun, moon illuminance levels in 2-hour increments. These don't match any
+    // real day anywhere.
+    static const uint32_t kTimeStep = 2;
+    static const float kSunlight[];
+    static const float kMoonlight[];
+    static const int kSunOverhead;
+    static const int kMoonOverhead;
+
+    // Illumination levels for various conditions, in lux
+    static const float kDirectSunIllum;
+    static const float kDaylightShadeIllum;
+    static const float kSunsetIllum;
+    static const float kTwilightIllum;
+    static const float kFullMoonIllum;
+    static const float kClearNightIllum;
+    static const float kStarIllum;
+    static const float kLivingRoomIllum;
+
+    // Chromaticity of various illumination sources
+    static const float kIncandescentXY[2];
+    static const float kDirectSunlightXY[2];
+    static const float kDaylightXY[2];
+    static const float kNoonSkyXY[2];
+    static const float kMoonlightXY[2];
+    static const float kSunsetXY[2];
+
+    static const uint8_t kSelfLit;
+    static const uint8_t kShadowed;
+    static const uint8_t kSky;
+
+    static const float kMaterials_xyY[NUM_MATERIALS][3];
+    static const uint8_t kMaterialsFlags[NUM_MATERIALS];
+
+    static const int kSceneWidth;
+    static const int kSceneHeight;
+    static const uint8_t kScene[];
+};
+
+}
+
+#endif // HW_EMULATOR_CAMERA2_SCENE_H
diff --git a/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp b/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp
new file mode 100644
index 0000000..73f1fb5
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Sensor.cpp
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+//#define LOG_NNDEBUG 0
+#define LOG_TAG "EmulatedCamera2_Sensor"
+
+#ifdef LOG_NNDEBUG
+#define ALOGVV(...) ALOGV(__VA_ARGS__)
+#else
+#define ALOGVV(...) ((void)0)
+#endif
+
+#include <utils/Log.h>
+
+#include "../EmulatedFakeCamera2.h"
+#include "Sensor.h"
+#include <cmath>
+#include <cstdlib>
+#include "system/camera_metadata.h"
+
+namespace android {
+
+const unsigned int Sensor::kResolution[2]  = {640, 480};
+
+const nsecs_t Sensor::kExposureTimeRange[2] =
+    {1000L, 30000000000L} ; // 1 us - 30 sec
+const nsecs_t Sensor::kFrameDurationRange[2] =
+    {33331760L, 30000000000L}; // ~1/30 s - 30 sec
+const nsecs_t Sensor::kMinVerticalBlank = 10000L;
+
+const uint8_t Sensor::kColorFilterArrangement = ANDROID_SENSOR_RGGB;
+
+// Output image data characteristics
+const uint32_t Sensor::kMaxRawValue = 4000;
+const uint32_t Sensor::kBlackLevel  = 1000;
+
+// Sensor sensitivity
+const float Sensor::kSaturationVoltage      = 0.520f;
+const uint32_t Sensor::kSaturationElectrons = 2000;
+const float Sensor::kVoltsPerLuxSecond      = 0.100f;
+
+const float Sensor::kElectronsPerLuxSecond =
+        Sensor::kSaturationElectrons / Sensor::kSaturationVoltage
+        * Sensor::kVoltsPerLuxSecond;
+
+const float Sensor::kBaseGainFactor = (float)Sensor::kMaxRawValue /
+            Sensor::kSaturationElectrons;
+
+const float Sensor::kReadNoiseStddevBeforeGain = 1.177; // in electrons
+const float Sensor::kReadNoiseStddevAfterGain =  2.100; // in digital counts
+const float Sensor::kReadNoiseVarBeforeGain =
+            Sensor::kReadNoiseStddevBeforeGain *
+            Sensor::kReadNoiseStddevBeforeGain;
+const float Sensor::kReadNoiseVarAfterGain =
+            Sensor::kReadNoiseStddevAfterGain *
+            Sensor::kReadNoiseStddevAfterGain;
+
+// While each row has to read out, reset, and then expose, the (reset +
+// expose) sequence can be overlapped by other row readouts, so the final
+// minimum frame duration is purely a function of row readout time, at least
+// if there's a reasonable number of rows.
+const nsecs_t Sensor::kRowReadoutTime =
+            Sensor::kFrameDurationRange[0] / Sensor::kResolution[1];
+
+const uint32_t Sensor::kAvailableSensitivities[5] =
+    {100, 200, 400, 800, 1600};
+const uint32_t Sensor::kDefaultSensitivity = 100;
+
+/** A few utility functions for math, normal distributions */
+
+// Take advantage of IEEE floating-point format to calculate an approximate
+// square root. Accurate to within +-3.6%
+float sqrtf_approx(float r) {
+    // Modifier is based on IEEE floating-point representation; the
+    // manipulations boil down to finding approximate log2, dividing by two, and
+    // then inverting the log2. A bias is added to make the relative error
+    // symmetric about the real answer.
+    const int32_t modifier = 0x1FBB4000;
+
+    int32_t r_i = *(int32_t*)(&r);
+    r_i = (r_i >> 1) + modifier;
+
+    return *(float*)(&r_i);
+}
+
+
+
+Sensor::Sensor(EmulatedFakeCamera2 *parent):
+        Thread(false),
+        mParent(parent),
+        mGotVSync(false),
+        mExposureTime(kFrameDurationRange[0]-kMinVerticalBlank),
+        mFrameDuration(kFrameDurationRange[0]),
+        mGainFactor(kDefaultSensitivity),
+        mNextBuffers(NULL),
+        mCapturedBuffers(NULL),
+        mScene(kResolution[0], kResolution[1], kElectronsPerLuxSecond)
+{
+
+}
+
+Sensor::~Sensor() {
+    shutDown();
+}
+
+status_t Sensor::startUp() {
+    ALOGV("%s: E", __FUNCTION__);
+
+    int res;
+    mCapturedBuffers = NULL;
+    res = run("EmulatedFakeCamera2::Sensor",
+            ANDROID_PRIORITY_URGENT_DISPLAY);
+
+    if (res != OK) {
+        ALOGE("Unable to start up sensor capture thread: %d", res);
+    }
+    return res;
+}
+
+status_t Sensor::shutDown() {
+    ALOGV("%s: E", __FUNCTION__);
+
+    int res;
+    res = requestExitAndWait();
+    if (res != OK) {
+        ALOGE("Unable to shut down sensor capture thread: %d", res);
+    }
+    return res;
+}
+
+Scene &Sensor::getScene() {
+    return mScene;
+}
+
+void Sensor::setExposureTime(uint64_t ns) {
+    Mutex::Autolock lock(mControlMutex);
+    ALOGVV("Exposure set to %f", ns/1000000.f);
+    mExposureTime = ns;
+}
+
+void Sensor::setFrameDuration(uint64_t ns) {
+    Mutex::Autolock lock(mControlMutex);
+    ALOGVV("Frame duration set to %f", ns/1000000.f);
+    mFrameDuration = ns;
+}
+
+void Sensor::setSensitivity(uint32_t gain) {
+    Mutex::Autolock lock(mControlMutex);
+    ALOGVV("Gain set to %d", gain);
+    mGainFactor = gain;
+}
+
+void Sensor::setDestinationBuffers(Buffers *buffers) {
+    Mutex::Autolock lock(mControlMutex);
+    mNextBuffers = buffers;
+}
+
+bool Sensor::waitForVSync(nsecs_t reltime) {
+    int res;
+    Mutex::Autolock lock(mControlMutex);
+
+    mGotVSync = false;
+    res = mVSync.waitRelative(mControlMutex, reltime);
+    if (res != OK && res != TIMED_OUT) {
+        ALOGE("%s: Error waiting for VSync signal: %d", __FUNCTION__, res);
+        return false;
+    }
+    return mGotVSync;
+}
+
+bool Sensor::waitForNewFrame(nsecs_t reltime,
+        nsecs_t *captureTime) {
+    Mutex::Autolock lock(mReadoutMutex);
+    uint8_t *ret;
+    if (mCapturedBuffers == NULL) {
+        int res;
+        res = mReadoutAvailable.waitRelative(mReadoutMutex, reltime);
+        if (res == TIMED_OUT) {
+            return false;
+        } else if (res != OK || mCapturedBuffers == NULL) {
+            ALOGE("Error waiting for sensor readout signal: %d", res);
+            return false;
+        }
+    } else {
+        mReadoutComplete.signal();
+    }
+
+    *captureTime = mCaptureTime;
+    mCapturedBuffers = NULL;
+    return true;
+}
+
+status_t Sensor::readyToRun() {
+    ALOGV("Starting up sensor thread");
+    mStartupTime = systemTime();
+    mNextCaptureTime = 0;
+    mNextCapturedBuffers = NULL;
+    return OK;
+}
+
+bool Sensor::threadLoop() {
+    /**
+     * Sensor capture operation main loop.
+     *
+     * Stages are out-of-order relative to a single frame's processing, but
+     * in-order in time.
+     */
+
+    /**
+     * Stage 1: Read in latest control parameters
+     */
+    uint64_t exposureDuration;
+    uint64_t frameDuration;
+    uint32_t gain;
+    Buffers *nextBuffers;
+    {
+        Mutex::Autolock lock(mControlMutex);
+        exposureDuration = mExposureTime;
+        frameDuration    = mFrameDuration;
+        gain             = mGainFactor;
+        nextBuffers      = mNextBuffers;
+        // Don't reuse a buffer set
+        mNextBuffers = NULL;
+
+        // Signal VSync for start of readout
+        ALOGVV("Sensor VSync");
+        mGotVSync = true;
+        mVSync.signal();
+    }
+
+    /**
+     * Stage 3: Read out latest captured image
+     */
+
+    Buffers *capturedBuffers = NULL;
+    nsecs_t captureTime = 0;
+
+    nsecs_t startRealTime  = systemTime();
+    // Stagefright cares about system time for timestamps, so base simulated
+    // time on that.
+    nsecs_t simulatedTime    = startRealTime;
+    nsecs_t frameEndRealTime = startRealTime + frameDuration;
+    nsecs_t frameReadoutEndRealTime = startRealTime +
+            kRowReadoutTime * kResolution[1];
+
+    if (mNextCapturedBuffers != NULL) {
+        ALOGVV("Sensor starting readout");
+        // Pretend we're doing readout now; will signal once enough time has elapsed
+        capturedBuffers = mNextCapturedBuffers;
+        captureTime    = mNextCaptureTime;
+    }
+    simulatedTime += kRowReadoutTime + kMinVerticalBlank;
+
+    // TODO: Move this signal to another thread to simulate readout
+    // time properly
+    if (capturedBuffers != NULL) {
+        ALOGVV("Sensor readout complete");
+        Mutex::Autolock lock(mReadoutMutex);
+        if (mCapturedBuffers != NULL) {
+            ALOGV("Waiting for readout thread to catch up!");
+            mReadoutComplete.wait(mReadoutMutex);
+        }
+
+        mCapturedBuffers = capturedBuffers;
+        mCaptureTime = captureTime;
+        mReadoutAvailable.signal();
+        capturedBuffers = NULL;
+    }
+
+    /**
+     * Stage 2: Capture new image
+     */
+
+    mNextCaptureTime = simulatedTime;
+    mNextCapturedBuffers = nextBuffers;
+
+    if (mNextCapturedBuffers != NULL) {
+        ALOGVV("Starting next capture: Exposure: %f ms, gain: %d",
+                (float)exposureDuration/1e6, gain);
+        mScene.setExposureDuration((float)exposureDuration/1e9);
+        mScene.calculateScene(mNextCaptureTime);
+
+        // Might be adding more buffers, so size isn't constant
+        for (size_t i = 0; i < mNextCapturedBuffers->size(); i++) {
+            const StreamBuffer &b = (*mNextCapturedBuffers)[i];
+            ALOGVV("Sensor capturing buffer %d: stream %d,"
+                    " %d x %d, format %x, stride %d, buf %p, img %p",
+                    i, b.streamId, b.width, b.height, b.format, b.stride,
+                    b.buffer, b.img);
+            switch(b.format) {
+                case HAL_PIXEL_FORMAT_RAW_SENSOR:
+                    captureRaw(b.img, gain, b.stride);
+                    break;
+                case HAL_PIXEL_FORMAT_RGB_888:
+                    captureRGB(b.img, gain, b.stride);
+                    break;
+                case HAL_PIXEL_FORMAT_RGBA_8888:
+                    captureRGBA(b.img, gain, b.stride);
+                    break;
+                case HAL_PIXEL_FORMAT_BLOB:
+                    // Add auxillary buffer of the right size
+                    // Assumes only one BLOB (JPEG) buffer in
+                    // mNextCapturedBuffers
+                    StreamBuffer bAux;
+                    bAux.streamId = 0;
+                    bAux.width = b.width;
+                    bAux.height = b.height;
+                    bAux.format = HAL_PIXEL_FORMAT_RGB_888;
+                    bAux.stride = b.width;
+                    bAux.buffer = NULL;
+                    // TODO: Reuse these
+                    bAux.img = new uint8_t[b.width * b.height * 3];
+                    mNextCapturedBuffers->push_back(bAux);
+                    break;
+                case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+                    captureNV21(b.img, gain, b.stride);
+                    break;
+                case HAL_PIXEL_FORMAT_YV12:
+                    // TODO:
+                    ALOGE("%s: Format %x is TODO", __FUNCTION__, b.format);
+                    break;
+                default:
+                    ALOGE("%s: Unknown format %x, no output", __FUNCTION__,
+                            b.format);
+                    break;
+            }
+        }
+    }
+
+    ALOGVV("Sensor vertical blanking interval");
+    nsecs_t workDoneRealTime = systemTime();
+    const nsecs_t timeAccuracy = 2e6; // 2 ms of imprecision is ok
+    if (workDoneRealTime < frameEndRealTime - timeAccuracy) {
+        timespec t;
+        t.tv_sec = (frameEndRealTime - workDoneRealTime)  / 1000000000L;
+        t.tv_nsec = (frameEndRealTime - workDoneRealTime) % 1000000000L;
+
+        int ret;
+        do {
+            ret = nanosleep(&t, &t);
+        } while (ret != 0);
+    }
+    nsecs_t endRealTime = systemTime();
+    ALOGVV("Frame cycle took %d ms, target %d ms",
+            (int)((endRealTime - startRealTime)/1000000),
+            (int)(frameDuration / 1000000));
+    return true;
+};
+
+void Sensor::captureRaw(uint8_t *img, uint32_t gain, uint32_t stride) {
+    float totalGain = gain/100.0 * kBaseGainFactor;
+    float noiseVarGain =  totalGain * totalGain;
+    float readNoiseVar = kReadNoiseVarBeforeGain * noiseVarGain
+            + kReadNoiseVarAfterGain;
+
+    int bayerSelect[4] = {Scene::R, Scene::Gr, Scene::Gb, Scene::B}; // RGGB
+    mScene.setReadoutPixel(0,0);
+    for (unsigned int y = 0; y < kResolution[1]; y++ ) {
+        int *bayerRow = bayerSelect + (y & 0x1) * 2;
+        uint16_t *px = (uint16_t*)img + y * stride;
+        for (unsigned int x = 0; x < kResolution[0]; x++) {
+            uint32_t electronCount;
+            electronCount = mScene.getPixelElectrons()[bayerRow[x & 0x1]];
+
+            // TODO: Better pixel saturation curve?
+            electronCount = (electronCount < kSaturationElectrons) ?
+                    electronCount : kSaturationElectrons;
+
+            // TODO: Better A/D saturation curve?
+            uint16_t rawCount = electronCount * totalGain;
+            rawCount = (rawCount < kMaxRawValue) ? rawCount : kMaxRawValue;
+
+            // Calculate noise value
+            // TODO: Use more-correct Gaussian instead of uniform noise
+            float photonNoiseVar = electronCount * noiseVarGain;
+            float noiseStddev = sqrtf_approx(readNoiseVar + photonNoiseVar);
+            // Scaled to roughly match gaussian/uniform noise stddev
+            float noiseSample = std::rand() * (2.5 / (1.0 + RAND_MAX)) - 1.25;
+
+            rawCount += kBlackLevel;
+            rawCount += noiseStddev * noiseSample;
+
+            *px++ = rawCount;
+        }
+        // TODO: Handle this better
+        //simulatedTime += kRowReadoutTime;
+    }
+    ALOGVV("Raw sensor image captured");
+}
+
+void Sensor::captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride) {
+    float totalGain = gain/100.0 * kBaseGainFactor;
+    // In fixed-point math, calculate total scaling from electrons to 8bpp
+    int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+    uint32_t inc = kResolution[0] / stride;
+
+    for (unsigned int y = 0, outY = 0; y < kResolution[1]; y+=inc, outY++ ) {
+        uint8_t *px = img + outY * stride * 4;
+        mScene.setReadoutPixel(0, y);
+        for (unsigned int x = 0; x < kResolution[0]; x+=inc) {
+            uint32_t rCount, gCount, bCount;
+            // TODO: Perfect demosaicing is a cheat
+            const uint32_t *pixel = mScene.getPixelElectrons();
+            rCount = pixel[Scene::R]  * scale64x;
+            gCount = pixel[Scene::Gr] * scale64x;
+            bCount = pixel[Scene::B]  * scale64x;
+
+            *px++ = rCount < 255*64 ? rCount / 64 : 255;
+            *px++ = gCount < 255*64 ? gCount / 64 : 255;
+            *px++ = bCount < 255*64 ? bCount / 64 : 255;
+            *px++ = 255;
+            for (unsigned int j = 1; j < inc; j++)
+                mScene.getPixelElectrons();
+        }
+        // TODO: Handle this better
+        //simulatedTime += kRowReadoutTime;
+    }
+    ALOGVV("RGBA sensor image captured");
+}
+
+void Sensor::captureRGB(uint8_t *img, uint32_t gain, uint32_t stride) {
+    float totalGain = gain/100.0 * kBaseGainFactor;
+    // In fixed-point math, calculate total scaling from electrons to 8bpp
+    int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+    uint32_t inc = kResolution[0] / stride;
+
+    for (unsigned int y = 0, outY = 0; y < kResolution[1]; y += inc, outY++ ) {
+        mScene.setReadoutPixel(0, y);
+        uint8_t *px = img + outY * stride * 3;
+        for (unsigned int x = 0; x < kResolution[0]; x += inc) {
+            uint32_t rCount, gCount, bCount;
+            // TODO: Perfect demosaicing is a cheat
+            const uint32_t *pixel = mScene.getPixelElectrons();
+            rCount = pixel[Scene::R]  * scale64x;
+            gCount = pixel[Scene::Gr] * scale64x;
+            bCount = pixel[Scene::B]  * scale64x;
+
+            *px++ = rCount < 255*64 ? rCount / 64 : 255;
+            *px++ = gCount < 255*64 ? gCount / 64 : 255;
+            *px++ = bCount < 255*64 ? bCount / 64 : 255;
+            for (unsigned int j = 1; j < inc; j++)
+                mScene.getPixelElectrons();
+        }
+        // TODO: Handle this better
+        //simulatedTime += kRowReadoutTime;
+    }
+    ALOGVV("RGB sensor image captured");
+}
+
+void Sensor::captureNV21(uint8_t *img, uint32_t gain, uint32_t stride) {
+    float totalGain = gain/100.0 * kBaseGainFactor;
+    // In fixed-point math, calculate total scaling from electrons to 8bpp
+    int scale64x = 64 * totalGain * 255 / kMaxRawValue;
+
+    // TODO: Make full-color
+    uint32_t inc = kResolution[0] / stride;
+    uint32_t outH = kResolution[1] / inc;
+    for (unsigned int y = 0, outY = 0, outUV = outH;
+         y < kResolution[1]; y+=inc, outY++, outUV ) {
+        uint8_t *pxY = img + outY * stride;
+        mScene.setReadoutPixel(0,y);
+        for (unsigned int x = 0; x < kResolution[0]; x+=inc) {
+            uint32_t rCount, gCount, bCount;
+            // TODO: Perfect demosaicing is a cheat
+            const uint32_t *pixel = mScene.getPixelElectrons();
+            rCount = pixel[Scene::R]  * scale64x;
+            gCount = pixel[Scene::Gr] * scale64x;
+            bCount = pixel[Scene::B]  * scale64x;
+            uint32_t avg = (rCount + gCount + bCount) / 3;
+            *pxY++ = avg < 255*64 ? avg / 64 : 255;
+            for (unsigned int j = 1; j < inc; j++)
+                mScene.getPixelElectrons();
+        }
+    }
+    for (unsigned int y = 0, outY = outH; y < kResolution[1]/2; y+=inc, outY++) {
+        uint8_t *px = img + outY * stride;
+        for (unsigned int x = 0; x < kResolution[0]; x+=inc) {
+            // UV to neutral
+            *px++ = 128;
+            *px++ = 128;
+        }
+    }
+    ALOGVV("NV21 sensor image captured");
+}
+
+} // namespace android
diff --git a/tools/emulator/system/camera/fake-pipeline2/Sensor.h b/tools/emulator/system/camera/fake-pipeline2/Sensor.h
new file mode 100644
index 0000000..ce7b4ad
--- /dev/null
+++ b/tools/emulator/system/camera/fake-pipeline2/Sensor.h
@@ -0,0 +1,221 @@
+/*
+ * 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 is a simple simulation of a typical CMOS cellphone imager chip,
+ * which outputs 12-bit Bayer-mosaic raw images.
+ *
+ * The sensor is abstracted as operating as a pipeline 3 stages deep;
+ * conceptually, each frame to be captured goes through these three stages. The
+ * processing step for the sensor is marked off by vertical sync signals, which
+ * indicate the start of readout of the oldest frame. The interval between
+ * processing steps depends on the frame duration of the frame currently being
+ * captured. The stages are 1) configure, 2) capture, and 3) readout. During
+ * configuration, the sensor's registers for settings such as exposure time,
+ * frame duration, and gain are set for the next frame to be captured. In stage
+ * 2, the image data for the frame is actually captured by the sensor. Finally,
+ * in stage 3, the just-captured data is read out and sent to the rest of the
+ * system.
+ *
+ * The sensor is assumed to be rolling-shutter, so low-numbered rows of the
+ * sensor are exposed earlier in time than larger-numbered rows, with the time
+ * offset between each row being equal to the row readout time.
+ *
+ * The characteristics of this sensor don't correspond to any actual sensor,
+ * but are not far off typical sensors.
+ *
+ * Example timing diagram, with three frames:
+ *  Frame 0-1: Frame duration 50 ms, exposure time 20 ms.
+ *  Frame   2: Frame duration 75 ms, exposure time 65 ms.
+ * Legend:
+ *   C = update sensor registers for frame
+ *   v = row in reset (vertical blanking interval)
+ *   E = row capturing image data
+ *   R = row being read out
+ *   | = vertical sync signal
+ *time(ms)|   0          55        105       155            230     270
+ * Frame 0|   :configure : capture : readout :              :       :
+ *  Row # | ..|CCCC______|_________|_________|              :       :
+ *      0 |   :\          \vvvvvEEEER         \             :       :
+ *    500 |   : \          \vvvvvEEEER         \            :       :
+ *   1000 |   :  \          \vvvvvEEEER         \           :       :
+ *   1500 |   :   \          \vvvvvEEEER         \          :       :
+ *   2000 |   :    \__________\vvvvvEEEER_________\         :       :
+ * Frame 1|   :           configure  capture      readout   :       :
+ *  Row # |   :          |CCCC_____|_________|______________|       :
+ *      0 |   :          :\         \vvvvvEEEER              \      :
+ *    500 |   :          : \         \vvvvvEEEER              \     :
+ *   1000 |   :          :  \         \vvvvvEEEER              \    :
+ *   1500 |   :          :   \         \vvvvvEEEER              \   :
+ *   2000 |   :          :    \_________\vvvvvEEEER______________\  :
+ * Frame 2|   :          :          configure     capture    readout:
+ *  Row # |   :          :         |CCCC_____|______________|_______|...
+ *      0 |   :          :         :\         \vEEEEEEEEEEEEER       \
+ *    500 |   :          :         : \         \vEEEEEEEEEEEEER       \
+ *   1000 |   :          :         :  \         \vEEEEEEEEEEEEER       \
+ *   1500 |   :          :         :   \         \vEEEEEEEEEEEEER       \
+ *   2000 |   :          :         :    \_________\vEEEEEEEEEEEEER_______\
+ */
+
+#ifndef HW_EMULATOR_CAMERA2_SENSOR_H
+#define HW_EMULATOR_CAMERA2_SENSOR_H
+
+#include "utils/Thread.h"
+#include "utils/Mutex.h"
+#include "utils/Timers.h"
+
+#include "Scene.h"
+#include "Base.h"
+
+namespace android {
+
+class EmulatedFakeCamera2;
+
+class Sensor: private Thread, public virtual RefBase {
+  public:
+
+    Sensor(EmulatedFakeCamera2 *parent);
+    ~Sensor();
+
+    /*
+     * Power control
+     */
+
+    status_t startUp();
+    status_t shutDown();
+
+    /*
+     * Access to scene
+     */
+    Scene &getScene();
+
+    /*
+     * Controls that can be updated every frame
+     */
+
+    void setExposureTime(uint64_t ns);
+    void setFrameDuration(uint64_t ns);
+    void setSensitivity(uint32_t gain);
+    // Buffer must be at least stride*height*2 bytes in size
+    void setDestinationBuffers(Buffers *buffers);
+
+    /*
+     * Controls that cause reconfiguration delay
+     */
+
+    void setBinning(int horizontalFactor, int verticalFactor);
+
+    /*
+     * Synchronizing with sensor operation (vertical sync)
+     */
+
+    // Wait until the sensor outputs its next vertical sync signal, meaning it
+    // is starting readout of its latest frame of data. Returns true if vertical
+    // sync is signaled, false if the wait timed out.
+    bool waitForVSync(nsecs_t reltime);
+
+    // Wait until a new frame has been read out, and then return the time
+    // capture started.  May return immediately if a new frame has been pushed
+    // since the last wait for a new frame. Returns true if new frame is
+    // returned, false if timed out.
+    bool waitForNewFrame(nsecs_t reltime,
+            nsecs_t *captureTime);
+
+    /**
+     * Static sensor characteristics
+     */
+    static const unsigned int kResolution[2];
+
+    static const nsecs_t kExposureTimeRange[2];
+    static const nsecs_t kFrameDurationRange[2];
+    static const nsecs_t kMinVerticalBlank;
+
+    static const uint8_t kColorFilterArrangement;
+
+    // Output image data characteristics
+    static const uint32_t kMaxRawValue;
+    static const uint32_t kBlackLevel;
+    // Sensor sensitivity, approximate
+
+    static const float kSaturationVoltage;
+    static const uint32_t kSaturationElectrons;
+    static const float kVoltsPerLuxSecond;
+    static const float kElectronsPerLuxSecond;
+
+    static const float kBaseGainFactor;
+
+    static const float kReadNoiseStddevBeforeGain; // In electrons
+    static const float kReadNoiseStddevAfterGain;  // In raw digital units
+    static const float kReadNoiseVarBeforeGain;
+    static const float kReadNoiseVarAfterGain;
+
+    // While each row has to read out, reset, and then expose, the (reset +
+    // expose) sequence can be overlapped by other row readouts, so the final
+    // minimum frame duration is purely a function of row readout time, at least
+    // if there's a reasonable number of rows.
+    static const nsecs_t kRowReadoutTime;
+
+    static const uint32_t kAvailableSensitivities[5];
+    static const uint32_t kDefaultSensitivity;
+
+  private:
+    EmulatedFakeCamera2 *mParent;
+
+    Mutex mControlMutex; // Lock before accessing control parameters
+    // Start of control parameters
+    Condition mVSync;
+    bool      mGotVSync;
+    uint64_t  mExposureTime;
+    uint64_t  mFrameDuration;
+    uint32_t  mGainFactor;
+    Buffers  *mNextBuffers;
+
+    // End of control parameters
+
+    Mutex mReadoutMutex; // Lock before accessing readout variables
+    // Start of readout variables
+    Condition mReadoutAvailable;
+    Condition mReadoutComplete;
+    Buffers  *mCapturedBuffers;
+    nsecs_t   mCaptureTime;
+    // End of readout variables
+
+    // Time of sensor startup, used for simulation zero-time point
+    nsecs_t mStartupTime;
+
+    /**
+     * Inherited Thread virtual overrides, and members only used by the
+     * processing thread
+     */
+  private:
+    virtual status_t readyToRun();
+
+    virtual bool threadLoop();
+
+    nsecs_t mNextCaptureTime;
+    Buffers *mNextCapturedBuffers;
+
+    Scene mScene;
+
+    void captureRaw(uint8_t *img, uint32_t gain, uint32_t stride);
+    void captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride);
+    void captureRGB(uint8_t *img, uint32_t gain, uint32_t stride);
+    void captureNV21(uint8_t *img, uint32_t gain, uint32_t stride);
+};
+
+}
+
+#endif // HW_EMULATOR_CAMERA2_SENSOR_H
diff --git a/tools/emulator/system/gps/gps_qemu.c b/tools/emulator/system/gps/gps_qemu.c
index eebe8d6..8f3d6e9 100644
--- a/tools/emulator/system/gps/gps_qemu.c
+++ b/tools/emulator/system/gps/gps_qemu.c
@@ -405,6 +405,16 @@
     return 0;
 }
 
+static int
+nmea_reader_update_accuracy( NmeaReader*  r )
+{
+    // Always return 20m accuracy.
+    // Possibly parse it from the NMEA sentence in the future.
+    r->fix.flags    |= GPS_LOCATION_HAS_ACCURACY;
+    r->fix.accuracy = 20;
+    return 0;
+}
+
 
 static void
 nmea_reader_parse( NmeaReader*  r )
@@ -488,6 +498,10 @@
         tok.p -= 2;
         D("unknown sentence '%.*s", tok.end-tok.p, tok.p);
     }
+
+    // Always update accuracy
+    nmea_reader_update_accuracy( r );
+
     if (r->fix.flags != 0) {
 #if GPS_DEBUG
         char   temp[256];
diff --git a/tools/idegen/README b/tools/idegen/README
index 1f773d8..02bb593 100644
--- a/tools/idegen/README
+++ b/tools/idegen/README
@@ -7,10 +7,10 @@
     If this is your first time using IDEGen...
 
         IDEA needs a lot of memory. Add "-Xms748m -Xmx748m" to your VM options
-        in "IDEA_HOME/bin/idea.vmoptions" on Linux or 
+        in "IDEA_HOME/bin/idea.vmoptions" on Linux or
         "IntelliJ IDEA.app/Contents/Info.plist" on OS X.
 
-        Create a JDK configuration named "1.5 (No Libraries)" by adding a new
+        Create a JDK configuration named "1.6 (No Libraries)" by adding a new
         JDK like you normally would and then removing all of the jar entries
         under the "Classpath" tab. This will ensure that you only get access to
         Android's core libraries and not those from your desktop VM.
@@ -18,13 +18,13 @@
     From the project's root directory...
 
         Repeat these steps after each sync...
-        
+
         1) make (to produce generated .java source)
         2) development/tools/idegen/idegen.sh
         3) Open android.ipr in IntelliJ. If you already have the project open,
            hit the sync button in IntelliJ, and it will automatically detect the
            updated configuration.
-        
+
         If you get unexpected compilation errors from IntelliJ, try running
         "Build -> Rebuild Project". Sometimes IntelliJ gets confused after the
         project changes significantly.
@@ -53,7 +53,7 @@
 
 Excluding source roots and jars
 
-    IDEGen keeps an exclusion list in the "excluded-paths" file. This file 
+    IDEGen keeps an exclusion list in the "excluded-paths" file. This file
     has one regular expression per line that matches paths (relative to the
     project root) that should be excluded from the IDE configuration. We
     use Java's regular expression parser (see java.util.regex.Parser).
@@ -62,7 +62,7 @@
     "excluded-paths" file in the project's root directory. For example, you
     might exclude all apps except the Browser in your IDE configuration with
     this regular expression: "^packages/apps/(?!Browser)".
-    
+
 Controlling source root ordering (Eclipse)
 
     You may want some source roots to come before others in Eclipse. Simply
@@ -77,4 +77,4 @@
     For example, if you want your applications's source root to come first,
     you might add an expression like "^packages/apps/MyApp/src$" to the top
     of the "path-precedence" file.  To make source roots under ./out come last,
-    add "^(?!out/)" (which matches all paths that don't start with "out/").
\ No newline at end of file
+    add "^(?!out/)" (which matches all paths that don't start with "out/").
diff --git a/tools/idegen/excluded-paths b/tools/idegen/excluded-paths
index 35280ad..9122c30 100644
--- a/tools/idegen/excluded-paths
+++ b/tools/idegen/excluded-paths
@@ -62,3 +62,6 @@
 # This directory contains only an R.java file which is the same as the one in
 # Camera_intermediates.
 ^out/target/common/obj/APPS/CameraTests_intermediates$
+
+# Exclude all prebuilts jars.
+^prebuilts/.*\.jar$
diff --git a/tools/idegen/idegen.ipr b/tools/idegen/idegen.ipr
index 00cf4fd..75771b5 100644
--- a/tools/idegen/idegen.ipr
+++ b/tools/idegen/idegen.ipr
@@ -135,7 +135,7 @@
     <option name="GENERATE_NO_WARNINGS" value="false" />
     <option name="DEPRECATION" value="true" />
     <option name="ADDITIONAL_OPTIONS_STRING" value="" />
-    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+    <option name="MAXIMUM_HEAP_SIZE" value="800" />
   </component>
   <component name="JavadocGenerationManager">
     <option name="OUTPUT_DIRECTORY" />
@@ -298,7 +298,7 @@
       <module fileurl="file://$PROJECT_DIR$/idegen.iml" filepath="$PROJECT_DIR$/idegen.iml" />
     </modules>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6 (no libraries)" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/classes" />
   </component>
   <component name="RmicSettings">
diff --git a/tools/idegen/templates/android.ipr b/tools/idegen/templates/android.ipr
index d6aba4b..f857d4a 100644
--- a/tools/idegen/templates/android.ipr
+++ b/tools/idegen/templates/android.ipr
@@ -26,7 +26,6 @@
         <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
       </value>
     </option>
-    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
   </component>
   <component name="CompilerConfiguration">
     <option name="DEFAULT_COMPILER" value="Javac" />
@@ -122,7 +121,7 @@
     <option name="GENERATE_NO_WARNINGS" value="false" />
     <option name="DEPRECATION" value="false" />
     <option name="ADDITIONAL_OPTIONS_STRING" value="-Xlint:all,-deprecation,-serial" />
-    <option name="MAXIMUM_HEAP_SIZE" value="512" />
+    <option name="MAXIMUM_HEAP_SIZE" value="800" />
   </component>
   <component name="JavadocGenerationManager">
     <option name="OUTPUT_DIRECTORY" />
@@ -282,7 +281,7 @@
       <module fileurl="file://$PROJECT_DIR$/android.iml" filepath="$PROJECT_DIR$/android.iml" />
     </modules>
   </component>
-  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="true" project-jdk-name="1.5 (No Libraries)" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.6 (No Libraries)" project-jdk-type="JavaSDK">
     <output url="file:///tmp/intellij$PROJECT_DIR$/classes" />
   </component>
   <component name="RmicSettings">
diff --git a/tools/make_key b/tools/make_key
index ac02d17..209d824 100755
--- a/tools/make_key
+++ b/tools/make_key
@@ -49,7 +49,7 @@
 read -p "Enter password for '$1' (blank for none; password will be visible): " \
   password
 
-( openssl genrsa -3 2048 | tee ${one} > ${two} ) &
+( openssl genrsa -f4 2048 | tee ${one} > ${two} ) &
 
 openssl req -new -x509 -sha1 -key ${two} -out $1.x509.pem \
   -days 10000 -subj "$2" &
diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk
new file mode 100644
index 0000000..937abd1
--- /dev/null
+++ b/tools/recovery_l10n/Android.mk
@@ -0,0 +1,12 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := RecoveryLocalizer
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
diff --git a/tools/recovery_l10n/AndroidManifest.xml b/tools/recovery_l10n/AndroidManifest.xml
new file mode 100644
index 0000000..8c51a4e
--- /dev/null
+++ b/tools/recovery_l10n/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.recovery_l10n">
+
+  <application android:label="Recovery Localizer">
+    <activity android:name="Main"
+              android:label="Recovery Localizer">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+
+</manifest>
+
+
diff --git a/tools/recovery_l10n/res/layout/main.xml b/tools/recovery_l10n/res/layout/main.xml
new file mode 100644
index 0000000..0900b11
--- /dev/null
+++ b/tools/recovery_l10n/res/layout/main.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              >
+
+  <Spinner android:id="@+id/which"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           />
+
+  <Button android:id="@+id/go"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="@string/go"
+          />
+
+  <TextView android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="#ffffffff"
+            android:background="#ff000000"
+            android:maxWidth="480px"
+            android:gravity="center"
+            />
+
+
+</LinearLayout>
+
+
diff --git a/tools/recovery_l10n/res/values-af/strings.xml b/tools/recovery_l10n/res/values-af/strings.xml
new file mode 100644
index 0000000..d526418
--- /dev/null
+++ b/tools/recovery_l10n/res/values-af/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installeer tans stelselopdatering..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Vee tans uit..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Geen bevel."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Fout!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-am/strings.xml b/tools/recovery_l10n/res/values-am/strings.xml
new file mode 100644
index 0000000..cddb099
--- /dev/null
+++ b/tools/recovery_l10n/res/values-am/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"የስርዓት ዝማኔ በመጫን ላይ…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"በመደምሰስ ላይ…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"ምንም ትዕዛዝ የለም።"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"ስህተት!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ar/strings.xml b/tools/recovery_l10n/res/values-ar/strings.xml
new file mode 100644
index 0000000..d06b966
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ar/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"جارٍ تثبيت تحديث النظام…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"جارٍ المسح…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"ليس هناك أي أمر."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"خطأ!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml
new file mode 100644
index 0000000..5f48a2d
--- /dev/null
+++ b/tools/recovery_l10n/res/values-be/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Усталёўка абнаўлення сістэмы..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Выдаленне..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Няма каманды"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Памылка"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-bg/strings.xml b/tools/recovery_l10n/res/values-bg/strings.xml
new file mode 100644
index 0000000..004f3b9
--- /dev/null
+++ b/tools/recovery_l10n/res/values-bg/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Системната актуализация се инсталира…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Изтрива се…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Без команда."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Грешка!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ca/strings.xml b/tools/recovery_l10n/res/values-ca/strings.xml
new file mode 100644
index 0000000..5d7b652
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ca/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"S\'està instal·lant l\'actualització del sistema..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"S\'està esborrant..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Cap ordre."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Error!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-cs/strings.xml b/tools/recovery_l10n/res/values-cs/strings.xml
new file mode 100644
index 0000000..771235d
--- /dev/null
+++ b/tools/recovery_l10n/res/values-cs/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instalace aktualizace systému..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Mazání…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Žádný příkaz."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Chyba!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-da/strings.xml b/tools/recovery_l10n/res/values-da/strings.xml
new file mode 100644
index 0000000..c28a76f
--- /dev/null
+++ b/tools/recovery_l10n/res/values-da/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Systemopdateringen installeres…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Sletter…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Ingen kommando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Fejl!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-de/strings.xml b/tools/recovery_l10n/res/values-de/strings.xml
new file mode 100644
index 0000000..02d2590
--- /dev/null
+++ b/tools/recovery_l10n/res/values-de/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Systemupdate wird installiert…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Wird gelöscht…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Kein Befehl"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Fehler"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-el/strings.xml b/tools/recovery_l10n/res/values-el/strings.xml
new file mode 100644
index 0000000..aa2626b
--- /dev/null
+++ b/tools/recovery_l10n/res/values-el/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Εγκατάσταση ενημέρωσης συστήματος…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Διαγραφή…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Καμία εντολή."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Σφάλμα!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-en-rGB/strings.xml b/tools/recovery_l10n/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..b70d678c
--- /dev/null
+++ b/tools/recovery_l10n/res/values-en-rGB/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installing system update…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Erasing…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"No command."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Error!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-es-rUS/strings.xml b/tools/recovery_l10n/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..256272a
--- /dev/null
+++ b/tools/recovery_l10n/res/values-es-rUS/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instalando actualización del sistema…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Borrando…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Ningún comando"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Error"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-es/strings.xml b/tools/recovery_l10n/res/values-es/strings.xml
new file mode 100644
index 0000000..323f055
--- /dev/null
+++ b/tools/recovery_l10n/res/values-es/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instalando actualización del sistema…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Borrando…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Sin comandos"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Error"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-et/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml
new file mode 100644
index 0000000..407a53d
--- /dev/null
+++ b/tools/recovery_l10n/res/values-et/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Süsteemivärskenduste installimine ..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Kustutamine ..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Käsk puudub."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Viga!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-fa/strings.xml b/tools/recovery_l10n/res/values-fa/strings.xml
new file mode 100644
index 0000000..dd002fa
--- /dev/null
+++ b/tools/recovery_l10n/res/values-fa/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"در حال نصب به‌روزرسانی سیستم ..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"پاک کردن..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"فرمانی موجود نیست."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"خطا!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-fi/strings.xml b/tools/recovery_l10n/res/values-fi/strings.xml
new file mode 100644
index 0000000..b77417a
--- /dev/null
+++ b/tools/recovery_l10n/res/values-fi/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Asennetaan järjestelmäpäivitystä..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Tyhjennetään..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Ei komentoa."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Virhe!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-fr/strings.xml b/tools/recovery_l10n/res/values-fr/strings.xml
new file mode 100644
index 0000000..cdb4a26
--- /dev/null
+++ b/tools/recovery_l10n/res/values-fr/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installation de la mise à jour du système en cours…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Effacement en cours…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Aucune commande."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Erreur !"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-hi/strings.xml b/tools/recovery_l10n/res/values-hi/strings.xml
new file mode 100644
index 0000000..3dfab3e
--- /dev/null
+++ b/tools/recovery_l10n/res/values-hi/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"सिस्टम अपडेट इंस्टॉल कर रहा है…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"मिटा रहा है…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"कोई आदेश नहीं."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"त्रुटि!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-hr/strings.xml b/tools/recovery_l10n/res/values-hr/strings.xml
new file mode 100644
index 0000000..56225c0
--- /dev/null
+++ b/tools/recovery_l10n/res/values-hr/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instaliranje ažuriranja sustava…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Brisanje…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nema naredbe."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Pogreška!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-hu/strings.xml b/tools/recovery_l10n/res/values-hu/strings.xml
new file mode 100644
index 0000000..a64f501
--- /dev/null
+++ b/tools/recovery_l10n/res/values-hu/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Rendszerfrissítés telepítése..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Törlés..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nincs parancs."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Hiba!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-in/strings.xml b/tools/recovery_l10n/res/values-in/strings.xml
new file mode 100644
index 0000000..93f9c28
--- /dev/null
+++ b/tools/recovery_l10n/res/values-in/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Memasang pembaruan sistem…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Menghapus..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Tidak ada perintah."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Kesalahan!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-it/strings.xml b/tools/recovery_l10n/res/values-it/strings.xml
new file mode 100644
index 0000000..9defe36
--- /dev/null
+++ b/tools/recovery_l10n/res/values-it/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installazione aggiornamento di sistema…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Cancellazione…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nessun comando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Errore!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-iw/strings.xml b/tools/recovery_l10n/res/values-iw/strings.xml
new file mode 100644
index 0000000..e43bb20
--- /dev/null
+++ b/tools/recovery_l10n/res/values-iw/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"מתקין עדכון מערכת…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"מוחק…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"אין פקודה."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"שגיאה!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ja/strings.xml b/tools/recovery_l10n/res/values-ja/strings.xml
new file mode 100644
index 0000000..da0fa62
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ja/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"システムアップデートをインストールしています…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"消去しています…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"コマンドが指定されていません。"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"エラーです"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ko/strings.xml b/tools/recovery_l10n/res/values-ko/strings.xml
new file mode 100644
index 0000000..e46a876
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ko/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"시스템 업데이트 설치 중…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"지우는 중…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"명령어가 없습니다."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"오류!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-lt/strings.xml b/tools/recovery_l10n/res/values-lt/strings.xml
new file mode 100644
index 0000000..957ac75
--- /dev/null
+++ b/tools/recovery_l10n/res/values-lt/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Diegiamas sistemos naujinys…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Ištrinama…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nėra komandos."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Klaida!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-lv/strings.xml b/tools/recovery_l10n/res/values-lv/strings.xml
new file mode 100644
index 0000000..c5d5b93
--- /dev/null
+++ b/tools/recovery_l10n/res/values-lv/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Notiek sistēmas atjauninājuma instalēšana..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Notiek dzēšana..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nav nevienas komandas."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Kļūda!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ms/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml
new file mode 100644
index 0000000..f563591
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ms/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Memasang kemas kini sistem..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Memadam..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Tiada arahan."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Ralat!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-nb/strings.xml b/tools/recovery_l10n/res/values-nb/strings.xml
new file mode 100644
index 0000000..4e89ad7
--- /dev/null
+++ b/tools/recovery_l10n/res/values-nb/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installerer systemoppdateringen ..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Sletter ..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Ingen kommando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Feil!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-nl/strings.xml b/tools/recovery_l10n/res/values-nl/strings.xml
new file mode 100644
index 0000000..be80a6b
--- /dev/null
+++ b/tools/recovery_l10n/res/values-nl/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Systeemupdate installeren…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Wissen…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Geen opdracht."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Fout!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-pl/strings.xml b/tools/recovery_l10n/res/values-pl/strings.xml
new file mode 100644
index 0000000..b1e5b7b
--- /dev/null
+++ b/tools/recovery_l10n/res/values-pl/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instaluję aktualizację systemu…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Usuwam…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Brak polecenia."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Błąd"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-pt-rPT/strings.xml b/tools/recovery_l10n/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..7d6bc18
--- /dev/null
+++ b/tools/recovery_l10n/res/values-pt-rPT/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"A instalar a atualização do sistema..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"A apagar…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nenhum comando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Erro!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-pt/strings.xml b/tools/recovery_l10n/res/values-pt/strings.xml
new file mode 100644
index 0000000..3cc5723
--- /dev/null
+++ b/tools/recovery_l10n/res/values-pt/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Instalando atualização do sistema..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Apagando..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nenhum comando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Erro!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ro/strings.xml b/tools/recovery_l10n/res/values-ro/strings.xml
new file mode 100644
index 0000000..f8acfe4
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ro/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Se instalează actualizarea de sistem…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Se efectuează ştergerea…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Nicio comandă."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Eroare!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-ru/strings.xml b/tools/recovery_l10n/res/values-ru/strings.xml
new file mode 100644
index 0000000..de0da40
--- /dev/null
+++ b/tools/recovery_l10n/res/values-ru/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Установка обновления системы…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Удаление…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Команды нет"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Ошибка"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-sk/strings.xml b/tools/recovery_l10n/res/values-sk/strings.xml
new file mode 100644
index 0000000..e55f83f
--- /dev/null
+++ b/tools/recovery_l10n/res/values-sk/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Prebieha inštalácia aktualizácie systému..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Prebieha mazanie..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Žiadny príkaz."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Chyba!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-sl/strings.xml b/tools/recovery_l10n/res/values-sl/strings.xml
new file mode 100644
index 0000000..3f8d46f
--- /dev/null
+++ b/tools/recovery_l10n/res/values-sl/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Namestitev posodobitve sistema ..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Brisanje ..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Ni ukaza"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Napaka"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-sr/strings.xml b/tools/recovery_l10n/res/values-sr/strings.xml
new file mode 100644
index 0000000..9553260
--- /dev/null
+++ b/tools/recovery_l10n/res/values-sr/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Инсталирање ажурирања система..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Брисање..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Нема команде."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Грешка!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-sv/strings.xml b/tools/recovery_l10n/res/values-sv/strings.xml
new file mode 100644
index 0000000..f875d30
--- /dev/null
+++ b/tools/recovery_l10n/res/values-sv/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Installerar systemuppdatering ..."</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Tar bort ..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Inget kommando."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Fel!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-sw/strings.xml b/tools/recovery_l10n/res/values-sw/strings.xml
new file mode 100644
index 0000000..1a53046
--- /dev/null
+++ b/tools/recovery_l10n/res/values-sw/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Inasakinisha sasisho la mfumo…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Inafuta…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Hakuna amri."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Hitilafu!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-th/strings.xml b/tools/recovery_l10n/res/values-th/strings.xml
new file mode 100644
index 0000000..bcdfa2b
--- /dev/null
+++ b/tools/recovery_l10n/res/values-th/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"กำลังติดตั้งการอัปเดตระบบ…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"กำลังลบ…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"ไม่มีคำสั่ง"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"ข้อผิดพลาด!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-tl/strings.xml b/tools/recovery_l10n/res/values-tl/strings.xml
new file mode 100644
index 0000000..be2ba26
--- /dev/null
+++ b/tools/recovery_l10n/res/values-tl/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Ini-install ang update sa system…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Binubura…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Walang command."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Error!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-tr/strings.xml b/tools/recovery_l10n/res/values-tr/strings.xml
new file mode 100644
index 0000000..8629029
--- /dev/null
+++ b/tools/recovery_l10n/res/values-tr/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Sistem güncellemesi yükleniyor…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Siliniyor…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Komut yok."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Hata!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-uk/strings.xml b/tools/recovery_l10n/res/values-uk/strings.xml
new file mode 100644
index 0000000..762c06f
--- /dev/null
+++ b/tools/recovery_l10n/res/values-uk/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Встановлення оновлення системи…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Стирання…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Немає команди."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Помилка!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-vi/strings.xml b/tools/recovery_l10n/res/values-vi/strings.xml
new file mode 100644
index 0000000..ab4005b
--- /dev/null
+++ b/tools/recovery_l10n/res/values-vi/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Đang cài đặt bản cập nhật hệ thống…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Đang xóa…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Không có lệnh nào."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Lỗi!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-zh-rCN/strings.xml b/tools/recovery_l10n/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..2e1a6f5
--- /dev/null
+++ b/tools/recovery_l10n/res/values-zh-rCN/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"正在安装系统更新…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"正在清除…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"无命令。"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"出错了!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-zh-rTW/strings.xml b/tools/recovery_l10n/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..f3f6a2c
--- /dev/null
+++ b/tools/recovery_l10n/res/values-zh-rTW/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"正在安裝系統更新…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"清除中..."</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"沒有指令。"</string>
+    <string name="recovery_error" msgid="4550265746256727080">"錯誤!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-zu/strings.xml b/tools/recovery_l10n/res/values-zu/strings.xml
new file mode 100644
index 0000000..1f904a2
--- /dev/null
+++ b/tools/recovery_l10n/res/values-zu/strings.xml
@@ -0,0 +1,8 @@
+<?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="recovery_installing" msgid="7864047928003865598">"Ifaka isibuyekezo sesistimu…"</string>
+    <string name="recovery_erasing" msgid="4612809744968710197">"Iyasula…"</string>
+    <string name="recovery_no_command" msgid="1915703879031023455">"Awukho umyalo."</string>
+    <string name="recovery_error" msgid="4550265746256727080">"Iphutha!"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml
new file mode 100644
index 0000000..3a8aeec
--- /dev/null
+++ b/tools/recovery_l10n/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <!-- Do not translate. -->
+  <string translatable="false" name="go">Go</string>
+
+  <!-- Do not translate. -->
+  <string-array translatable="false" name="string_options">
+    <item>installing</item>
+    <item>erasing</item>
+    <item>no_command</item>
+    <item>error</item>
+  </string-array>
+
+  <!-- Displayed on the screen beneath the animated android while the
+       system is installing an update. [CHAR LIMIT=60] -->
+  <string name="recovery_installing">Installing system update\u2026</string>
+
+  <!-- Displayed on the screen beneath the animated android while the
+       system is erasing a partition (either a data wipe aka "factory
+       reset", or a cache wipe). [CHAR LIMIT=60] -->
+  <string name="recovery_erasing">Erasing\u2026</string>
+
+  <!-- Displayed on the screen when the user has gotten into recovery
+       mode without a command to run.  Will not normally happen, but
+       users (especially developers) may boot into recovery mode
+       manually via special key combinations.  [CHAR LIMIT=60] -->
+  <string name="recovery_no_command">No command.</string>
+
+  <!-- Displayed on the triangle-! screen when a system update
+       installation or data wipe procedure encounters an error.  [CHAR
+       LIMIT=60] -->
+  <string name="recovery_error">Error!</string>
+
+</resources>
diff --git a/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
new file mode 100644
index 0000000..3f2bebe
--- /dev/null
+++ b/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.recovery_l10n;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Spinner;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * This activity assists in generating the specially-formatted bitmaps
+ * of text needed for recovery's localized text display.  Each image
+ * contains all the translations of a single string; above each
+ * translation is a "header row" that encodes that subimage's width,
+ * height, and locale using pixel values.
+ *
+ * To use this app to generate new translations:
+ *
+ *   - Update the string resources in res/values-*
+ *
+ *   - Build and run the app.  Select the string you want to
+ *     translate, and press the "Go" button.
+ *
+ *   - Wait for it to finish cycling through all the strings, then
+ *     pull /data/data/com.android.recovery_l10n/files/text-out.png
+ *     from the device.
+ *
+ *   - "pngcrush -c 0 text-out.png output.png"
+ *
+ *   - Put output.png in bootable/recovery/res/images/ (renamed
+ *     appropriately).
+ *
+ * Recovery expects 8-bit 1-channel images (white text on black
+ * background).  pngcrush -c 0 will convert the output of this program
+ * to such an image.  If you use any other image handling tools,
+ * remember that they must be lossless to preserve the exact values of
+ * pixels in the header rows; don't convert them to jpeg or anything.
+ */
+
+public class Main extends Activity {
+    private static final String TAG = "RecoveryL10N";
+
+    HashMap<Locale, Bitmap> savedBitmaps;
+    TextView mText;
+    int mStringId = R.string.recovery_installing;
+
+    public class TextCapture implements Runnable {
+        private Locale nextLocale;
+        private Locale thisLocale;
+        private Runnable next;
+
+        TextCapture(Locale thisLocale, Locale nextLocale, Runnable next) {
+            this.nextLocale = nextLocale;
+            this.thisLocale = thisLocale;
+            this.next = next;
+        }
+
+        public void run() {
+            Bitmap b = mText.getDrawingCache();
+            savedBitmaps.put(thisLocale, b.copy(Bitmap.Config.ARGB_8888, false));
+
+            if (nextLocale != null) {
+                switchTo(nextLocale);
+            }
+
+            if (next != null) {
+                mText.postDelayed(next, 200);
+            }
+        }
+    }
+
+    private void switchTo(Locale locale) {
+        Resources standardResources = getResources();
+        AssetManager assets = standardResources.getAssets();
+        DisplayMetrics metrics = standardResources.getDisplayMetrics();
+        Configuration config = new Configuration(standardResources.getConfiguration());
+        config.locale = locale;
+        Resources defaultResources = new Resources(assets, metrics, config);
+
+        mText.setText(mStringId);
+
+        mText.setDrawingCacheEnabled(false);
+        mText.setDrawingCacheEnabled(true);
+        mText.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstance) {
+        super.onCreate(savedInstance);
+        setContentView(R.layout.main);
+
+        savedBitmaps = new HashMap<Locale, Bitmap>();
+
+        Spinner spinner = (Spinner) findViewById(R.id.which);
+        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
+            this, R.array.string_options, android.R.layout.simple_spinner_item);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spinner.setAdapter(adapter);
+        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView parent, View view,
+                                       int pos, long id) {
+                switch (pos) {
+                    case 0: mStringId = R.string.recovery_installing; break;
+                    case 1: mStringId = R.string.recovery_erasing; break;
+                    case 2: mStringId = R.string.recovery_no_command; break;
+                    case 3: mStringId = R.string.recovery_error; break;
+                }
+            }
+            @Override public void onNothingSelected(AdapterView parent) { }
+            });
+
+        mText = (TextView) findViewById(R.id.text);
+
+        String[] localeNames = getAssets().getLocales();
+        Arrays.sort(localeNames);
+        ArrayList<Locale> locales = new ArrayList<Locale>();
+        for (String ln : localeNames) {
+            int u = ln.indexOf('_');
+            if (u >= 0) {
+                Log.i(TAG, "locale = " + ln);
+                locales.add(new Locale(ln.substring(0, u), ln.substring(u+1)));
+            }
+        }
+
+        final Runnable seq = buildSequence(locales.toArray(new Locale[0]));
+
+        Button b = (Button) findViewById(R.id.go);
+        b.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View ignore) {
+                mText.post(seq);
+            }
+            });
+    }
+
+    private Runnable buildSequence(final Locale[] locales) {
+        Runnable head = new Runnable() { public void run() { mergeBitmaps(locales); } };
+        Locale prev = null;
+        for (Locale loc : locales) {
+            head = new TextCapture(loc, prev, head);
+            prev = loc;
+        }
+        final Runnable fhead = head;
+        final Locale floc = prev;
+        return new Runnable() { public void run() { startSequence(fhead, floc); } };
+    }
+
+    private void startSequence(Runnable firstRun, Locale firstLocale) {
+        savedBitmaps.clear();
+        switchTo(firstLocale);
+        mText.postDelayed(firstRun, 200);
+    }
+
+    private void saveBitmap(Bitmap b, String filename) {
+        try {
+            FileOutputStream fos = openFileOutput(filename, 0);
+            b.compress(Bitmap.CompressFormat.PNG, 100, fos);
+            fos.close();
+        } catch (IOException e) {
+            Log.i(TAG, "failed to write PNG", e);
+        }
+    }
+
+    private int colorFor(byte b) {
+        return 0xff000000 | (b<<16) | (b<<8) | b;
+    }
+
+    private int colorFor(int b) {
+        return 0xff000000 | (b<<16) | (b<<8) | b;
+    }
+
+    private void mergeBitmaps(final Locale[] locales) {
+        HashMap<String, Integer> countByLanguage = new HashMap<String, Integer>();
+
+        int height = 2;
+        int width = 10;
+        int maxHeight = 0;
+        for (Locale loc : locales) {
+            Bitmap b = savedBitmaps.get(loc);
+            int h = b.getHeight();
+            int w = b.getWidth();
+            height += h+1;
+            if (h > maxHeight) maxHeight = h;
+            if (w > width) width = w;
+
+            String lang = loc.getLanguage();
+            if (countByLanguage.containsKey(lang)) {
+                countByLanguage.put(lang, countByLanguage.get(lang)+1);
+            } else {
+                countByLanguage.put(lang, 1);
+            }
+        }
+
+        Log.i(TAG, "output bitmap is " + width + " x " + height);
+        Bitmap out = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        out.eraseColor(0xff000000);
+        int[] pixels = new int[maxHeight * width];
+
+        int p = 0;
+        for (Locale loc : locales) {
+            Bitmap bm = savedBitmaps.get(loc);
+            int h = bm.getHeight();
+            int w = bm.getWidth();
+
+            bm.getPixels(pixels, 0, w, 0, 0, w, h);
+
+            // Find the rightmost and leftmost columns with any
+            // nonblack pixels; we'll copy just that region to the
+            // output image.
+
+            int right = w;
+            while (right > 1) {
+                boolean all_black = true;
+                for (int j = 0; j < h; ++j) {
+                    if (pixels[j*w+right-1] != 0xff000000) {
+                        all_black = false;
+                        break;
+                    }
+                }
+                if (all_black) {
+                    --right;
+                } else {
+                    break;
+                }
+            }
+
+            int left = 0;
+            while (left < right-1) {
+                boolean all_black = true;
+                for (int j = 0; j < h; ++j) {
+                    if (pixels[j*w+left] != 0xff000000) {
+                        all_black = false;
+                        break;
+                    }
+                }
+                if (all_black) {
+                    ++left;
+                } else {
+                    break;
+                }
+            }
+
+            // Make the last country variant for a given language be
+            // the catch-all for that language (because recovery will
+            // take the first one that matches).
+            String lang = loc.getLanguage();
+            if (countByLanguage.get(lang) > 1) {
+                countByLanguage.put(lang, countByLanguage.get(lang)-1);
+                lang = loc.toString();
+            }
+            int tw = right - left;
+            Log.i(TAG, "encoding \"" + loc + "\" as \"" + lang + "\": " + tw + " x " + h);
+            byte[] langBytes = lang.getBytes();
+            out.setPixel(0, p, colorFor(tw & 0xff));
+            out.setPixel(1, p, colorFor(tw >>> 8));
+            out.setPixel(2, p, colorFor(h & 0xff));
+            out.setPixel(3, p, colorFor(h >>> 8));
+            out.setPixel(4, p, colorFor(langBytes.length));
+            int x = 5;
+            for (byte b : langBytes) {
+                out.setPixel(x, p, colorFor(b));
+                x++;
+            }
+            out.setPixel(x, p, colorFor(0));
+
+            p++;
+
+            out.setPixels(pixels, left, w, 0, p, tw, h);
+            p += h;
+        }
+
+        // if no languages match, suppress text display by using a
+        // single black pixel as the image.
+        out.setPixel(0, p, colorFor(1));
+        out.setPixel(1, p, colorFor(0));
+        out.setPixel(2, p, colorFor(1));
+        out.setPixel(3, p, colorFor(0));
+        out.setPixel(4, p, colorFor(0));
+        p++;
+
+        saveBitmap(out, "text-out.png");
+        Log.i(TAG, "wrote text-out.png");
+    }
+}